diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e447294eb7a..a2aeb014a00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,9 +17,6 @@ sha: v1.3.4 hooks: - id: reorder-python-imports - language_version: 'python2.7' + language_version: 'python3.7' args: - - --add-import - - from __future__ import absolute_import - - --add-import - - from __future__ import unicode_literals + - --py3-plus diff --git a/Dockerfile b/Dockerfile index 918c6876b9d..ca0093b9f39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,6 @@ RUN apk add --no-cache \ musl-dev \ openssl \ openssl-dev \ - python2 \ - python2-dev \ zlib-dev ENV BUILD_BOOTLOADER=1 @@ -40,7 +38,6 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ libssl-dev \ make \ openssl \ - python2.7-dev \ zlib1g-dev FROM build-${BUILD_PLATFORM} AS build diff --git a/bin/docker-compose b/bin/docker-compose index aeb53870303..5976e1d4aa5 100755 --- a/bin/docker-compose +++ b/bin/docker-compose @@ -1,6 +1,3 @@ #!/usr/bin/env python -from __future__ import absolute_import -from __future__ import unicode_literals - from compose.cli.main import main main() diff --git a/compose/__init__.py b/compose/__init__.py index 6e5fcaae99b..449e29da495 100644 --- a/compose/__init__.py +++ b/compose/__init__.py @@ -1,4 +1,2 @@ -from __future__ import absolute_import -from __future__ import unicode_literals __version__ = '1.26.0-rc1' diff --git a/compose/__main__.py b/compose/__main__.py index 27a7acbb8d1..199ba2ae9b4 100644 --- a/compose/__main__.py +++ b/compose/__main__.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from compose.cli.main import main main() diff --git a/compose/cli/colors.py b/compose/cli/colors.py index ea45198e07d..c6a869bf59d 100644 --- a/compose/cli/colors.py +++ b/compose/cli/colors.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from ..const import IS_WINDOWS_PLATFORM NAMES = [ diff --git a/compose/cli/command.py b/compose/cli/command.py index 7621134e8f7..f18f7663995 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -1,12 +1,7 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import os import re -import six - from . import errors from .. import config from .. import parallel @@ -110,7 +105,7 @@ def get_config_from_options(base_dir, options, additional_options=None): def get_config_path_from_options(base_dir, options, environment): def unicode_paths(paths): - return [p.decode('utf-8') if isinstance(p, six.binary_type) else p for p in paths] + return [p.decode('utf-8') if isinstance(p, bytes) else p for p in paths] file_option = options.get('--file') if file_option: diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index d4cdc96e8f5..493597b8071 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -1,11 +1,7 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import os.path import ssl -import six from docker import APIClient from docker import Context from docker import ContextAPI @@ -47,7 +43,7 @@ def get_client(environment, verbose=False, version=None, context=None): environment=environment, tls_version=get_tls_version(environment) ) if verbose: - version_info = six.iteritems(client.version()) + version_info = client.version().items() log.info(get_version_info('full')) log.info("Docker base_url: %s", client.base_url) log.info("Docker version: %s", diff --git a/compose/cli/docopt_command.py b/compose/cli/docopt_command.py index 809a4b7455e..d0ba7f67026 100644 --- a/compose/cli/docopt_command.py +++ b/compose/cli/docopt_command.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from inspect import getdoc from docopt import docopt @@ -14,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 @@ -53,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 189b67faf67..d1a47f07833 100644 --- a/compose/cli/errors.py +++ b/compose/cli/errors.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import contextlib import logging import socket diff --git a/compose/cli/formatter.py b/compose/cli/formatter.py index 9651fb4da44..0bde6b4a2c6 100644 --- a/compose/cli/formatter.py +++ b/compose/cli/formatter.py @@ -1,10 +1,6 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import shutil -import six import texttable from compose.cli import colors @@ -57,7 +53,7 @@ def get_level_message(self, record): return '' def format(self, record): - if isinstance(record.msg, six.binary_type): + if isinstance(record.msg, bytes): record.msg = record.msg.decode('utf-8') - message = super(ConsoleWarningFormatter, self).format(record) + message = super().format(record) return '{0}{1}'.format(self.get_level_message(record), message) diff --git a/compose/cli/log_printer.py b/compose/cli/log_printer.py index a4b70a67205..ca46b975e72 100644 --- a/compose/cli/log_printer.py +++ b/compose/cli/log_printer.py @@ -1,23 +1,19 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - +import _thread as thread import sys from collections import namedtuple from itertools import cycle +from queue import Empty +from queue import Queue from threading import Thread from docker.errors import APIError -from six.moves import _thread as thread -from six.moves.queue import Empty -from six.moves.queue import Queue from . import colors -from compose import utils from compose.cli.signals import ShutdownException from compose.utils import split_buffer -class LogPresenter(object): +class LogPresenter: def __init__(self, prefix_width, color_func): self.prefix_width = prefix_width @@ -54,7 +50,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, @@ -67,7 +63,7 @@ def __init__(self, self.containers = containers self.presenters = presenters self.event_stream = event_stream - self.output = utils.get_output_stream(output) + self.output = output self.cascade_stop = cascade_stop self.log_args = log_args or {} diff --git a/compose/cli/main.py b/compose/cli/main.py index e226a600869..5ec6181e4c4 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import contextlib import functools import json @@ -180,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: diff --git a/compose/cli/signals.py b/compose/cli/signals.py index 44def2ece65..0244e70189a 100644 --- a/compose/cli/signals.py +++ b/compose/cli/signals.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import signal from ..const import IS_WINDOWS_PLATFORM diff --git a/compose/cli/utils.py b/compose/cli/utils.py index 931487a6cdc..d1c9bae100b 100644 --- a/compose/cli/utils.py +++ b/compose/cli/utils.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import unicode_literals - import math import os import platform @@ -10,7 +6,6 @@ import sys import docker -import six import compose from ..const import IS_WINDOWS_PLATFORM @@ -144,7 +139,7 @@ def human_readable_file_size(size): def binarystr_to_unicode(s): - if not isinstance(s, six.binary_type): + if not isinstance(s, bytes): return s if IS_WINDOWS_PLATFORM: diff --git a/compose/cli/verbose_proxy.py b/compose/cli/verbose_proxy.py index b1592eabe73..22a97e6d02e 100644 --- a/compose/cli/verbose_proxy.py +++ b/compose/cli/verbose_proxy.py @@ -1,17 +1,12 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import functools import logging import pprint from itertools import chain -import six - def format_call(args, kwargs): args = (repr(a) for a in args) - kwargs = ("{0!s}={1!r}".format(*item) for item in six.iteritems(kwargs)) + kwargs = ("{0!s}={1!r}".format(*item) for item in kwargs.items()) return "({0})".format(", ".join(chain(args, kwargs))) @@ -27,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. """ @@ -41,7 +36,7 @@ def __init__(self, obj_name, obj, log_name=None, max_lines=10): def __getattr__(self, name): attr = getattr(self.obj, name) - if not six.callable(attr): + if not callable(attr): return attr return functools.partial(self.proxy_callable, name) diff --git a/compose/config/__init__.py b/compose/config/__init__.py index 2b40666f149..855b2401d94 100644 --- a/compose/config/__init__.py +++ b/compose/config/__init__.py @@ -1,7 +1,4 @@ # flake8: noqa -from __future__ import absolute_import -from __future__ import unicode_literals - from . import environment from .config import ConfigurationError from .config import DOCKER_CONFIG_KEYS diff --git a/compose/config/config.py b/compose/config/config.py index 56761d18146..0fbe6406b44 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import functools import io import logging @@ -11,7 +8,6 @@ from collections import namedtuple from operator import attrgetter -import six import yaml from cached_property import cached_property @@ -204,7 +200,7 @@ def version(self): 'Compose file version 1.'.format(self.filename)) return V1 - if not isinstance(version, six.string_types): + if not isinstance(version, str): raise ConfigurationError( 'Version in "{}" is invalid - it should be a string.' .format(self.filename)) @@ -589,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 @@ -688,12 +684,12 @@ def resolve_environment(service_dict, environment=None): env.update(env_vars_from_file(env_file)) env.update(parse_environment(service_dict.get('environment'))) - return dict(resolve_env_var(k, v, environment) for k, v in six.iteritems(env)) + return dict(resolve_env_var(k, v, environment) for k, v in env.items()) def resolve_build_args(buildargs, environment): args = parse_build_arguments(buildargs) - return dict(resolve_env_var(k, v, environment) for k, v in six.iteritems(args)) + return dict(resolve_env_var(k, v, environment) for k, v in args.items()) def validate_extended_service_dict(service_dict, filename, service): @@ -780,7 +776,7 @@ def process_service(service_config): def process_build_section(service_dict, working_dir): - if isinstance(service_dict['build'], six.string_types): + if isinstance(service_dict['build'], str): service_dict['build'] = resolve_build_path(working_dir, service_dict['build']) elif isinstance(service_dict['build'], dict): if 'context' in service_dict['build']: @@ -848,7 +844,7 @@ def process_healthcheck(service_dict): hc['test'] = ['NONE'] for field in ['interval', 'timeout', 'start_period']: - if field not in hc or isinstance(hc[field], six.integer_types): + if field not in hc or isinstance(hc[field], int): continue hc[field] = parse_nanoseconds_int(hc[field]) @@ -1179,7 +1175,7 @@ def parse_sequence_func(seq): def merge_build(output, base, override): def to_dict(service): build_config = service.get('build', {}) - if isinstance(build_config, six.string_types): + if isinstance(build_config, str): return {'context': build_config} return build_config @@ -1389,7 +1385,7 @@ def normalize_build(service_dict, working_dir, environment): if 'build' in service_dict: build = {} # Shortcut where specifying a string is treated as the build context - if isinstance(service_dict['build'], six.string_types): + if isinstance(service_dict['build'], str): build['context'] = service_dict.pop('build') else: build.update(service_dict['build']) @@ -1415,7 +1411,7 @@ def validate_paths(service_dict): if 'build' in service_dict: build = service_dict.get('build', {}) - if isinstance(build, six.string_types): + if isinstance(build, str): build_path = build elif isinstance(build, dict) and 'context' in build: build_path = build['context'] @@ -1506,7 +1502,7 @@ def merge_list_or_string(base, override): def to_list(value): if value is None: return [] - elif isinstance(value, six.string_types): + elif isinstance(value, str): return [value] else: return value diff --git a/compose/config/environment.py b/compose/config/environment.py index 6afbfc97244..2f3932db50e 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -1,12 +1,8 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import os import re import dotenv -import six from ..const import IS_WINDOWS_PLATFORM from .errors import ConfigurationError @@ -16,7 +12,7 @@ def split_env(env): - if isinstance(env, six.binary_type): + if isinstance(env, bytes): env = env.decode('utf-8', 'replace') key = value = None if '=' in env: @@ -44,7 +40,7 @@ def env_vars_from_file(filename): class Environment(dict): def __init__(self, *args, **kwargs): - super(Environment, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.missing_keys = [] self.silent = False @@ -82,11 +78,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: @@ -99,20 +95,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 9b2078f2c6d..3e8a834d004 100644 --- a/compose/config/errors.py +++ b/compose/config/errors.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals VERSION_EXPLANATION = ( @@ -42,7 +40,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? @@ -53,7 +51,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 18be8562cbb..a4c9a574479 100644 --- a/compose/config/interpolation.py +++ b/compose/config/interpolation.py @@ -1,12 +1,7 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import re from string import Template -import six - from .errors import ConfigurationError from compose.const import COMPOSEFILE_V2_0 as V2_0 from compose.utils import parse_bytes @@ -16,7 +11,7 @@ log = logging.getLogger(__name__) -class Interpolator(object): +class Interpolator: def __init__(self, templater, mapping): self.templater = templater @@ -77,7 +72,7 @@ def recursive_interpolate(obj, interpolator, config_path): def append(config_path, key): return '{}/{}'.format(config_path, key) - if isinstance(obj, six.string_types): + if isinstance(obj, str): return converter.convert(config_path, interpolator.interpolate(obj)) if isinstance(obj, dict): return dict( @@ -138,7 +133,7 @@ def convert(mo): if named is not None: val = mapping[named] - if isinstance(val, six.binary_type): + if isinstance(val, bytes): val = val.decode('utf-8') return '%s' % (val,) if mo.group('escaped') is not None: @@ -177,7 +172,7 @@ def service_path(*args): def to_boolean(s): - if not isinstance(s, six.string_types): + if not isinstance(s, str): return s s = s.lower() if s in ['y', 'yes', 'true', 'on']: @@ -188,11 +183,11 @@ def to_boolean(s): def to_int(s): - if not isinstance(s, six.string_types): + if not isinstance(s, str): return s # We must be able to handle octal representation for `mode` values notably - if six.PY3 and re.match('^0[0-9]+$', s.strip()): + if re.match('^0[0-9]+$', s.strip()): s = '0o' + s[1:] try: return int(s, base=0) @@ -201,7 +196,7 @@ def to_int(s): def to_float(s): - if not isinstance(s, six.string_types): + if not isinstance(s, str): return s try: @@ -224,12 +219,12 @@ def bytes_to_int(s): def to_microseconds(v): - if not isinstance(v, six.string_types): + if not isinstance(v, str): return 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 5776ce957cb..fe0d007a60f 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - -import six import yaml from compose.config import types @@ -15,7 +11,7 @@ def serialize_config_type(dumper, data): - representer = dumper.represent_str if six.PY3 else dumper.represent_unicode + representer = dumper.represent_str return representer(data.repr()) @@ -25,9 +21,9 @@ def serialize_dict_type(dumper, data): def serialize_string(dumper, data): """ Ensure boolean-like strings are quoted in the output """ - representer = dumper.represent_str if six.PY3 else dumper.represent_unicode + representer = dumper.represent_str - if isinstance(data, six.binary_type): + if isinstance(data, bytes): data = data.decode('utf-8') if data.lower() in ('y', 'n', 'yes', 'no', 'on', 'off', 'true', 'false'): @@ -98,10 +94,10 @@ def v3_introduced_name_key(key): def serialize_config(config, image_digests=None, escape_dollar=True): if escape_dollar: yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar) - yaml.SafeDumper.add_representer(six.text_type, serialize_string_escape_dollar) + yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar) else: yaml.SafeDumper.add_representer(str, serialize_string) - yaml.SafeDumper.add_representer(six.text_type, serialize_string) + yaml.SafeDumper.add_representer(str, serialize_string) return yaml.safe_dump( denormalize_config(config, image_digests), default_flow_style=False, diff --git a/compose/config/sort_services.py b/compose/config/sort_services.py index 42f548a6dd1..a600139b2ac 100644 --- a/compose/config/sort_services.py +++ b/compose/config/sort_services.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from compose.config.errors import DependencyError diff --git a/compose/config/types.py b/compose/config/types.py index ab8f34e3d7f..bbd74c6773a 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -1,16 +1,12 @@ """ Types for objects parsed from the configuration. """ -from __future__ import absolute_import -from __future__ import unicode_literals - import json import ntpath import os import re from collections import namedtuple -import six from docker.utils.ports import build_port_bindings from ..const import COMPOSEFILE_V1 as V1 @@ -104,7 +100,7 @@ def serialize_restart_spec(restart_spec): return '' parts = [restart_spec['Name']] if restart_spec['MaximumRetryCount']: - parts.append(six.text_type(restart_spec['MaximumRetryCount'])) + parts.append(str(restart_spec['MaximumRetryCount'])) return ':'.join(parts) @@ -150,7 +146,7 @@ def normpath(path, win_host=False): return path -class MountSpec(object): +class MountSpec: options_map = { 'volume': { 'nocopy': 'no_copy' @@ -326,7 +322,7 @@ def merge_field(self): class ServiceConfigBase(namedtuple('_ServiceConfigBase', 'source target uid gid mode name')): @classmethod def parse(cls, spec): - if isinstance(spec, six.string_types): + if isinstance(spec, str): return cls(spec, None, None, None, None, None) return cls( spec.get('source'), @@ -364,7 +360,7 @@ def __new__(cls, target, published, *args, **kwargs): raise ConfigurationError('Invalid target port: {}'.format(target)) if published: - if isinstance(published, six.string_types) and '-' in published: # "x-y:z" format + if isinstance(published, str) and '-' in published: # "x-y:z" format a, b = published.split('-', 1) try: int(a) @@ -477,7 +473,7 @@ def normalize_port_dict(port): class SecurityOpt(namedtuple('_SecurityOpt', 'value src_file')): @classmethod def parse(cls, value): - if not isinstance(value, six.string_types): + if not isinstance(value, str): return value # based on https://github.com/docker/cli/blob/9de1b162f/cli/command/container/opts.go#L673-L697 con = value.split('=', 2) diff --git a/compose/config/validation.py b/compose/config/validation.py index 1cceb71f0a4..3161b239533 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -1,13 +1,9 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import json import logging import os import re import sys -import six from docker.utils.ports import split_port from jsonschema import Draft4Validator from jsonschema import FormatChecker @@ -75,13 +71,13 @@ def format_ports(instance): try: split_port(instance) except ValueError as e: - raise ValidationError(six.text_type(e)) + raise ValidationError(str(e)) return True @FormatChecker.cls_checks(format="expose", raises=ValidationError) def format_expose(instance): - if isinstance(instance, six.string_types): + if isinstance(instance, str): if not re.match(VALID_EXPOSE_FORMAT, instance): raise ValidationError( "should be of the format 'PORT[/PROTOCOL]'") @@ -91,7 +87,7 @@ def format_expose(instance): @FormatChecker.cls_checks("subnet_ip_address", raises=ValidationError) def format_subnet_ip_address(instance): - if isinstance(instance, six.string_types): + if isinstance(instance, str): if not re.match(VALID_REGEX_IPV4_CIDR, instance) and \ not re.match(VALID_REGEX_IPV6_CIDR, instance): raise ValidationError("should use the CIDR format") @@ -138,7 +134,7 @@ def validate_config_section(filename, config, section): type=anglicize_json_type(python_type_to_yaml_type(config)))) for key, value in config.items(): - if not isinstance(key, six.string_types): + if not isinstance(key, str): raise ConfigurationError( "In file '{filename}', the {section} name {name} must be a " "quoted string, i.e. '{name}'.".format( @@ -166,7 +162,7 @@ def validate_top_level_object(config_file): def validate_ulimits(service_config): ulimit_config = service_config.config.get('ulimits', {}) - for limit_name, soft_hard_values in six.iteritems(ulimit_config): + for limit_name, soft_hard_values in ulimit_config.items(): if isinstance(soft_hard_values, dict): if not soft_hard_values['soft'] <= soft_hard_values['hard']: raise ConfigurationError( @@ -329,7 +325,7 @@ def handle_generic_error(error, path): required_keys) elif error.cause: - error_msg = six.text_type(error.cause) + error_msg = str(error.cause) msg_format = "{path} is invalid: {msg}" elif error.path: @@ -349,7 +345,7 @@ def parse_key_from_error_msg(error): def path_string(path): - return ".".join(c for c in path if isinstance(c, six.string_types)) + return ".".join(c for c in path if isinstance(c, str)) def _parse_valid_types_from_validator(validator): diff --git a/compose/const.py b/compose/const.py index 3d9030520b0..d80e23c9ef8 100644 --- a/compose/const.py +++ b/compose/const.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import sys from .version import ComposeVersion diff --git a/compose/container.py b/compose/container.py index 8a2fb240e0d..2571a6417b1 100644 --- a/compose/container.py +++ b/compose/container.py @@ -1,9 +1,5 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from functools import reduce -import six from docker.errors import ImageNotFound from .const import LABEL_CONTAINER_NUMBER @@ -16,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. @@ -130,7 +126,7 @@ def format_port(private, public): return ', '.join( ','.join(format_port(*item)) - for item in sorted(six.iteritems(self.ports)) + for item in sorted(self.ports.items()) ) @property diff --git a/compose/errors.py b/compose/errors.py index 415b41e7f04..44b6ba82150 100644 --- a/compose/errors.py +++ b/compose/errors.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals class OperationFailedError(Exception): @@ -19,14 +17,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 84531ecc7b8..3541dab6894 100644 --- a/compose/network.py +++ b/compose/network.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import re from collections import OrderedDict @@ -27,7 +24,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): @@ -170,7 +167,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 ) @@ -261,7 +258,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 {} diff --git a/compose/parallel.py b/compose/parallel.py index e242a318ae7..acf9e4a84cf 100644 --- a/compose/parallel.py +++ b/compose/parallel.py @@ -1,18 +1,15 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - +import _thread as thread import logging import operator import sys +from queue import Empty +from queue import Queue from threading import Lock from threading import Semaphore from threading import Thread from docker.errors import APIError from docker.errors import ImageNotFound -from six.moves import _thread as thread -from six.moves.queue import Empty -from six.moves.queue import Queue from compose.cli.colors import green from compose.cli.colors import red @@ -21,7 +18,6 @@ from compose.errors import HealthCheckFailed from compose.errors import NoHealthCheckConfigured from compose.errors import OperationFailedError -from compose.utils import get_output_stream log = logging.getLogger(__name__) @@ -29,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. """ @@ -85,7 +81,7 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None, limit=None, fa in the CLI logs, but don't raise an exception (such as attempting to start 0 containers) """ objects = list(objects) - stream = get_output_stream(sys.stderr) + stream = sys.stderr if ParallelStreamWriter.instance: writer = ParallelStreamWriter.instance @@ -118,7 +114,7 @@ def _no_deps(x): return [] -class State(object): +class State: """ Holds the state of a partially-complete parallel operation. @@ -140,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 @@ -256,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 522ddf75d19..8792ff287cb 100644 --- a/compose/progress_stream.py +++ b/compose/progress_stream.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from compose import utils @@ -18,7 +15,7 @@ def write_to_stream(s, stream): def stream_output(output, stream): is_terminal = hasattr(stream, 'isatty') and stream.isatty() - stream = utils.get_output_stream(stream) + stream = stream lines = {} diff = 0 diff --git a/compose/project.py b/compose/project.py index 696c8b04023..7e82fffbdac 100644 --- a/compose/project.py +++ b/compose/project.py @@ -1,15 +1,11 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import datetime +import enum import logging import operator import re from functools import reduce from os import path -import enum -import six from docker.errors import APIError from docker.errors import ImageNotFound from docker.errors import NotFound @@ -65,7 +61,7 @@ def update_labels(cls, value, labels): raise ValueError("Invalid value for one_off: {}".format(repr(value))) -class Project(object): +class Project: """ A collection of services. """ @@ -394,7 +390,7 @@ def build_service(service): ) if len(errors): combined_errors = '\n'.join([ - e.decode('utf-8') if isinstance(e, six.binary_type) else e for e in errors.values() + e.decode('utf-8') if isinstance(e, bytes) else e for e in errors.values() ]) raise ProjectError(combined_errors) @@ -684,7 +680,7 @@ def pull_service(service): .format(' '.join(must_build))) if len(errors): combined_errors = '\n'.join([ - e.decode('utf-8') if isinstance(e, six.binary_type) else e for e in errors.values() + e.decode('utf-8') if isinstance(e, bytes) else e for e in errors.values() ]) raise ProjectError(combined_errors) @@ -934,7 +930,7 @@ def __init__(self, image_name, service_name): class NoSuchService(Exception): def __init__(self, name): - if isinstance(name, six.binary_type): + if isinstance(name, bytes): name = name.decode('utf-8') self.name = name self.msg = "No such service: %s" % self.name diff --git a/compose/service.py b/compose/service.py index ebe237b8cfc..b8d1de1c7a4 100644 --- a/compose/service.py +++ b/compose/service.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - +import enum import itertools import json import logging import os import re +import subprocess import sys import tempfile from collections import namedtuple from collections import OrderedDict from operator import attrgetter -import enum -import six from docker.errors import APIError from docker.errors import ImageNotFound from docker.errors import NotFound @@ -62,11 +59,6 @@ from .utils import unique_everseen from compose.cli.utils import binarystr_to_unicode -if six.PY2: - import subprocess32 as subprocess -else: - import subprocess - log = logging.getLogger(__name__) HOST_CONFIG_KEYS = [ @@ -170,7 +162,7 @@ class BuildAction(enum.Enum): skip = 2 -class Service(object): +class Service: def __init__( self, name, @@ -425,7 +417,7 @@ def _containers_have_diverged(self, containers): except NoSuchImageError as e: log.debug( 'Service %s has diverged: %s', - self.name, six.text_type(e), + self.name, str(e), ) return True @@ -975,7 +967,7 @@ def _get_container_host_config(self, override_options, one_off=False): blkio_config = convert_blkio_config(options.get('blkio_config', None)) log_config = get_log_config(logging_dict) init_path = None - if isinstance(options.get('init'), six.string_types): + if isinstance(options.get('init'), str): init_path = options.get('init') options['init'] = True @@ -1109,7 +1101,7 @@ def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_a try: all_events = list(stream_output(build_output, output_stream)) except StreamOutputError as e: - raise BuildError(self, six.text_type(e)) + raise BuildError(self, str(e)) # Ensure the HTTP connection is not reused for another # streaming command, as the Docker daemon can sometimes @@ -1224,7 +1216,7 @@ def _do_pull(self, repo, pull_kwargs, silent, ignore_pull_failures): if not ignore_pull_failures: raise else: - log.error(six.text_type(e)) + log.error(str(e)) def pull(self, ignore_pull_failures=False, silent=False, stream=False): if 'image' not in self.options: @@ -1265,7 +1257,7 @@ def push(self, ignore_push_failures=False): if not ignore_push_failures: raise else: - log.error(six.text_type(e)) + log.error(str(e)) def is_healthy(self): """ Check that all containers for this service report healthy. @@ -1317,7 +1309,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 @@ -1357,7 +1349,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 @@ -1372,7 +1364,7 @@ def id(self): mode = id -class ContainerNetworkMode(object): +class ContainerNetworkMode: """A network mode that uses a container's network stack.""" service_name = None @@ -1389,7 +1381,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): @@ -1631,8 +1623,8 @@ def build_ulimits(ulimit_config): if not ulimit_config: return None ulimits = [] - for limit_name, soft_hard_values in six.iteritems(ulimit_config): - if isinstance(soft_hard_values, six.integer_types): + for limit_name, soft_hard_values in ulimit_config.items(): + if isinstance(soft_hard_values, int): ulimits.append({'name': limit_name, 'soft': soft_hard_values, 'hard': soft_hard_values}) elif isinstance(soft_hard_values, dict): ulimit_dict = {'name': limit_name} @@ -1656,7 +1648,7 @@ def format_environment(environment): def format_env(key, value): if value is None: return key - if isinstance(value, six.binary_type): + if isinstance(value, bytes): value = value.decode('utf-8') return '{key}={value}'.format(key=key, value=value) @@ -1707,18 +1699,13 @@ def convert_blkio_config(blkio_config): def rewrite_build_path(path): - # python2 os.stat() doesn't support unicode on some UNIX, so we - # encode it to a bytestring to be safe - if not six.PY3 and not IS_WINDOWS_PLATFORM: - path = path.encode('utf8') - if IS_WINDOWS_PLATFORM and not is_url(path) and not path.startswith(WINDOWS_LONGPATH_PREFIX): path = WINDOWS_LONGPATH_PREFIX + os.path.normpath(path) return path -class _CLIBuilder(object): +class _CLIBuilder: def __init__(self, progress): self._progress = progress @@ -1808,9 +1795,6 @@ def build(self, path, tag=None, quiet=False, fileobj=None, line = p.stdout.readline() if not line: break - # Fix non ascii chars on Python2. To remove when #6890 is complete. - if six.PY2: - magic_word = str(magic_word) if line.startswith(magic_word): appear = True yield json.dumps({"stream": line}) @@ -1828,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 16ef8a6dc91..2b6077e8fc9 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 @@ -31,9 +30,6 @@ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import absolute_import -from __future__ import unicode_literals - import re HOURS = r'(?P[\d.]+)h' @@ -93,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 a1e5e6435d8..09c272e7e89 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -1,14 +1,9 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - -import codecs import hashlib import json.decoder import logging import ntpath import random -import six from docker.errors import DockerException from docker.utils import parse_bytes as sdk_parse_bytes @@ -21,12 +16,6 @@ log = logging.getLogger(__name__) -def get_output_stream(stream): - if six.PY3: - return stream - return codecs.getwriter('utf-8')(stream) - - def stream_as_text(stream): """Given a stream of bytes or text, if any of the items in the stream are bytes convert them to text. @@ -35,13 +24,13 @@ def stream_as_text(stream): of byte streams. """ for data in stream: - if not isinstance(data, six.text_type): + if not isinstance(data, str): data = data.decode('utf-8', 'replace') yield data def line_splitter(buffer, separator=u'\n'): - index = buffer.find(six.text_type(separator)) + index = buffer.find(str(separator)) if index == -1: return None return buffer[:index + 1], buffer[index + 1:] @@ -56,7 +45,7 @@ def split_buffer(stream, splitter=None, decoder=lambda a: a): of the input. """ splitter = splitter or line_splitter - buffered = six.text_type('') + buffered = '' for data in stream_as_text(stream): buffered += data diff --git a/compose/version.py b/compose/version.py index 0532e16c717..c039263acb9 100644 --- a/compose/version.py +++ b/compose/version.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from distutils.version import LooseVersion diff --git a/compose/volume.py b/compose/volume.py index b02fc5d8030..39e40382570 100644 --- a/compose/volume.py +++ b/compose/volume.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import re @@ -18,7 +15,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 @@ -99,7 +96,7 @@ def _set_legacy_flag(self): self.legacy = False -class ProjectVolumes(object): +class ProjectVolumes: def __init__(self, volumes): self.volumes = volumes @@ -177,7 +174,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 ' diff --git a/contrib/migration/migrate-compose-file-v1-to-v2.py b/contrib/migration/migrate-compose-file-v1-to-v2.py index 274b499b9d4..e217b7072c3 100755 --- a/contrib/migration/migrate-compose-file-v1-to-v2.py +++ b/contrib/migration/migrate-compose-file-v1-to-v2.py @@ -3,9 +3,6 @@ Migrate a Compose file from the V1 format in Compose 1.5 to the V2 format supported by Compose 1.6+ """ -from __future__ import absolute_import -from __future__ import unicode_literals - import argparse import logging import sys diff --git a/docker-compose_darwin.spec b/docker-compose_darwin.spec index 344c070d501..5937f881ae6 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 b1bc694427f..d0b258d530d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ backports.shutil_get_terminal_size==1.0.0 -backports.ssl-match-hostname==3.5.0.1; python_version < '3' cached-property==1.5.1 certifi==2019.11.28 chardet==3.0.4 @@ -8,8 +7,6 @@ docker==4.2.0 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 @@ -20,8 +17,5 @@ PySocks==1.7.1 python-dotenv==0.11.0 PyYAML==5.3 requests==2.22.0 -six==1.12.0 -subprocess32==3.5.4; python_version < '3.2' texttable==1.6.2 -urllib3==1.25.7; python_version == '3.3' websocket-client==0.57.0 diff --git a/script/release/const.py b/script/release/const.py index 77a32332a7f..8c90eebca6e 100644 --- a/script/release/const.py +++ b/script/release/const.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import os diff --git a/script/release/release.py b/script/release/release.py index f53d1f3c181..c8e5e7f767d 100755 --- a/script/release/release.py +++ b/script/release/release.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -from __future__ import absolute_import -from __future__ import unicode_literals - import re import click diff --git a/script/release/utils.py b/script/release/utils.py index 4f57704872b..25b39ca7497 100644 --- a/script/release/utils.py +++ b/script/release/utils.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import os import re diff --git a/script/test/all b/script/test/all index f929a57eecc..57b76d5a988 100755 --- a/script/test/all +++ b/script/test/all @@ -11,7 +11,7 @@ docker run --rm \ "$TAG" tox -e pre-commit get_versions="docker run --rm - --entrypoint=/code/.tox/py27/bin/python + --entrypoint=/code/.tox/py37/bin/python $TAG /code/script/test/versions.py docker/docker-ce,moby/moby" @@ -23,7 +23,7 @@ fi BUILD_NUMBER=${BUILD_NUMBER-$USER} -PY_TEST_VERSIONS=${PY_TEST_VERSIONS:-py27,py37} +PY_TEST_VERSIONS=${PY_TEST_VERSIONS:-py37} for version in $DOCKER_VERSIONS; do >&2 echo "Running tests against Docker $version" diff --git a/script/test/versions.py b/script/test/versions.py index a06c49f20b0..1a28dc19ad3 100755 --- a/script/test/versions.py +++ b/script/test/versions.py @@ -21,10 +21,6 @@ `default` would return `1.7.1` and `recent -n 3` would return `1.8.0-rc2 1.7.1 1.6.2` """ -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import argparse import itertools import operator diff --git a/setup.py b/setup.py index 548e2bc9398..0b106eee478 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import codecs import os import re @@ -38,7 +33,6 @@ def find_version(*file_paths): 'websocket-client >= 0.32.0, < 1', 'docker[ssh] >= 3.7.0, < 5', 'dockerpty >= 0.4.1, < 1', - 'six >= 1.3.0, < 2', 'jsonschema >= 2.5.1, < 4', ] @@ -97,7 +91,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'], }, @@ -106,8 +100,6 @@ def find_version(*file_paths): 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.6', diff --git a/tests/__init__.py b/tests/__init__.py index 1ac1b21cf74..9d732490098 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,14 +1,2 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - -import sys - -if sys.version_info >= (2, 7): - import unittest # NOQA -else: - import unittest2 as unittest # NOQA - -try: - from unittest import mock -except ImportError: - import mock # NOQA +import unittest # NOQA +from unittest import mock # NOQA diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 43d5a3f5ccc..a5e8a47bce0 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import datetime import json import os.path @@ -15,7 +11,6 @@ from operator import attrgetter import pytest -import six import yaml from docker import errors @@ -103,7 +98,7 @@ def kill_service(service): container.kill() -class ContainerCountCondition(object): +class ContainerCountCondition: def __init__(self, project, expected): self.project = project @@ -116,7 +111,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 @@ -144,7 +139,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 @@ -166,7 +161,7 @@ def tearDown(self): if hasattr(self, '_project'): del self._project - super(CLITestCase, self).tearDown() + super().tearDown() @property def project(self): @@ -2220,15 +2215,9 @@ def test_run_handles_sighup(self): @mock.patch.dict(os.environ) def test_run_unicode_env_values_from_system(self): value = 'ą, ć, ę, ł, ń, ó, ś, ź, ż' - if six.PY2: # os.environ doesn't support unicode values in Py2 - os.environ['BAR'] = value.encode('utf-8') - else: # ... and doesn't support byte values in Py3 - os.environ['BAR'] = value + os.environ['BAR'] = value self.base_dir = 'tests/fixtures/unicode-environment' - result = self.dispatch(['run', 'simple']) - - if six.PY2: # Can't retrieve output on Py3. See issue #3670 - assert value in result.stdout.strip() + self.dispatch(['run', 'simple']) container = self.project.containers(one_off=OneOffFilter.only, stopped=True)[0] environment = container.get('Config.Env') diff --git a/tests/acceptance/context_test.py b/tests/acceptance/context_test.py index 1d79a22a042..a5d0c14730f 100644 --- a/tests/acceptance/context_test.py +++ b/tests/acceptance/context_test.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals - import os import shutil import unittest diff --git a/tests/helpers.py b/tests/helpers.py index 1365c5bcfb3..d178684853b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import contextlib import os diff --git a/tests/integration/environment_test.py b/tests/integration/environment_test.py index 671e65318a6..43df2c52b03 100644 --- a/tests/integration/environment_test.py +++ b/tests/integration/environment_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import tempfile from ddt import data diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py index a2493fda1a2..23c9e9a4bda 100644 --- a/tests/integration/network_test.py +++ b/tests/integration/network_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from .testcases import DockerClientTestCase diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index cb620a8c972..19d27185c9b 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import copy import json import os diff --git a/tests/integration/resilience_test.py b/tests/integration/resilience_test.py index 3de16e977b8..2fbaafb2894 100644 --- a/tests/integration/resilience_test.py +++ b/tests/integration/resilience_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from .. import mock @@ -25,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 c50aab08bb2..0b81e384e6e 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -1,18 +1,14 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import os import re import shutil import tempfile from distutils.spawn import find_executable +from io import StringIO from os import path import pytest from docker.errors import APIError from docker.errors import ImageNotFound -from six import StringIO -from six import text_type from .. import mock from ..helpers import BUSYBOX_IMAGE_WITH_TAG @@ -1015,7 +1011,7 @@ def test_build_non_ascii_filename(self): with open(os.path.join(base_dir.encode('utf8'), b'foo\xE2bar'), 'w') as f: f.write("hello world\n") - service = self.create_service('web', build={'context': text_type(base_dir)}) + service = self.create_service('web', build={'context': str(base_dir)}) service.build() self.addCleanup(self.client.remove_image, service.image_name) assert self.client.inspect_image('composetest_web') @@ -1049,7 +1045,7 @@ def test_build_with_build_args(self): f.write("RUN echo ${build_version}\n") service = self.create_service('buildwithargs', - build={'context': text_type(base_dir), + build={'context': str(base_dir), 'args': {"build_version": "1"}}) service.build() self.addCleanup(self.client.remove_image, service.image_name) @@ -1066,7 +1062,7 @@ def test_build_with_build_args_override(self): f.write("RUN echo ${build_version}\n") service = self.create_service('buildwithargs', - build={'context': text_type(base_dir), + build={'context': str(base_dir), 'args': {"build_version": "1"}}) service.build(build_args_override={'build_version': '2'}) self.addCleanup(self.client.remove_image, service.image_name) @@ -1082,7 +1078,7 @@ def test_build_with_build_labels(self): f.write('FROM busybox\n') service = self.create_service('buildlabels', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), 'labels': {'com.docker.compose.test': 'true'} }) service.build() @@ -1109,7 +1105,7 @@ def test_build_with_network(self): self.client.start(net_container) service = self.create_service('buildwithnet', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), 'network': 'container:{}'.format(net_container['Id']) }) @@ -1133,7 +1129,7 @@ def test_build_with_target(self): f.write('LABEL com.docker.compose.test.target=two\n') service = self.create_service('buildtarget', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), 'target': 'one' }) @@ -1155,7 +1151,7 @@ def test_build_with_extra_hosts(self): ])) service = self.create_service('build_extra_hosts', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), 'extra_hosts': { 'foobar': '127.0.0.1', 'baz': '127.0.0.1' @@ -1177,7 +1173,7 @@ def test_build_with_gzip(self): f.write('hello world\n') service = self.create_service('build_gzip', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), }) service.build(gzip=True) assert service.image() @@ -1190,7 +1186,7 @@ def test_build_with_isolation(self): f.write('FROM busybox\n') service = self.create_service('build_isolation', build={ - 'context': text_type(base_dir), + 'context': str(base_dir), 'isolation': 'default', }) service.build() @@ -1204,7 +1200,7 @@ def test_build_with_illegal_leading_chars(self): service = Service( 'build_leading_slug', client=self.client, project='___-composetest', build={ - 'context': text_type(base_dir) + 'context': str(base_dir) } ) assert service.image_name == 'composetest_build_leading_slug' diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py index 492de7b8ab1..2eb5d61f1c6 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -2,9 +2,6 @@ Integration tests which cover state convergence (aka smart recreate) performed by `docker-compose up`. """ -from __future__ import absolute_import -from __future__ import unicode_literals - import copy import os import shutil @@ -42,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'}, @@ -98,7 +95,7 @@ def test_all_change(self): class ProjectWithDependenciesTest(ProjectTestCase): def setUp(self): - super(ProjectWithDependenciesTest, self).setUp() + super().setUp() self.cfg = { 'db': { @@ -216,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', diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index fe70d1f7246..f787923adde 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import functools import os diff --git a/tests/integration/volume_test.py b/tests/integration/volume_test.py index 2a521d4c5b1..0e7c78bc25a 100644 --- a/tests/integration/volume_test.py +++ b/tests/integration/volume_test.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - -import six from docker.errors import DockerException from .testcases import DockerClientTestCase @@ -22,12 +18,12 @@ 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: custom_name = True - if isinstance(external, six.text_type): + if isinstance(external, str): name = external vol = Volume( diff --git a/tests/unit/cli/command_test.py b/tests/unit/cli/command_test.py index 3a9844c4f7c..9d4db5b59a8 100644 --- a/tests/unit/cli/command_test.py +++ b/tests/unit/cli/command_test.py @@ -1,11 +1,6 @@ -# ~*~ encoding: utf-8 ~*~ -from __future__ import absolute_import -from __future__ import unicode_literals - import os import pytest -import six from compose.cli.command import get_config_path_from_options from compose.config.environment import Environment @@ -13,7 +8,7 @@ from tests import mock -class TestGetConfigPathFromOptions(object): +class TestGetConfigPathFromOptions: def test_path_from_options(self): paths = ['one.yml', 'two.yml'] @@ -65,12 +60,3 @@ def test_unicode_path_from_options(self): assert get_config_path_from_options( '.', opts, environment ) == ['就吃饭/docker-compose.yml'] - - @pytest.mark.skipif(six.PY3, reason='Env values in Python 3 are already Unicode') - def test_unicode_path_from_env(self): - with mock.patch.dict(os.environ): - os.environ['COMPOSE_FILE'] = b'\xe5\xb0\xb1\xe5\x90\x83\xe9\xa5\xad/docker-compose.yml' - environment = Environment.from_env_file('.') - assert get_config_path_from_options( - '.', {}, environment - ) == ['就吃饭/docker-compose.yml'] diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py index 772c136eefd..41a50377cc2 100644 --- a/tests/unit/cli/docker_client_test.py +++ b/tests/unit/cli/docker_client_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import os import platform import ssl @@ -233,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 7b53ed2b15f..740ae33739b 100644 --- a/tests/unit/cli/errors_test.py +++ b/tests/unit/cli/errors_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from docker.errors import APIError from requests.exceptions import ConnectionError @@ -24,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): diff --git a/tests/unit/cli/formatter_test.py b/tests/unit/cli/formatter_test.py index e685725112f..07f5a8f50c5 100644 --- a/tests/unit/cli/formatter_test.py +++ b/tests/unit/cli/formatter_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging from compose.cli import colors diff --git a/tests/unit/cli/log_printer_test.py b/tests/unit/cli/log_printer_test.py index 5e387241d67..d970cda2058 100644 --- a/tests/unit/cli/log_printer_test.py +++ b/tests/unit/cli/log_printer_test.py @@ -1,13 +1,10 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import itertools +from io import StringIO +from queue import Queue import pytest import requests -import six from docker.errors import APIError -from six.moves.queue import Queue from compose.cli.log_printer import build_log_generator from compose.cli.log_printer import build_log_presenters @@ -22,7 +19,7 @@ @pytest.fixture def output_stream(): - output = six.StringIO() + output = StringIO() output.flush = mock.Mock() return output @@ -32,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) @@ -86,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 @@ -128,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'}] @@ -170,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 067c74f0b0e..b023f779f1d 100644 --- a/tests/unit/cli/main_test.py +++ b/tests/unit/cli/main_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import logging import docker @@ -35,7 +32,7 @@ def logging_handler(): return logging.StreamHandler(stream=stream) -class TestCLIMainTestCase(object): +class TestCLIMainTestCase: def test_filter_attached_containers(self): containers = [ @@ -138,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) @@ -158,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} @@ -192,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/utils_test.py b/tests/unit/cli/utils_test.py index 7a762890370..d67c8ba8aff 100644 --- a/tests/unit/cli/utils_test.py +++ b/tests/unit/cli/utils_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import unittest from compose.cli.utils import human_readable_file_size diff --git a/tests/unit/cli/verbose_proxy_test.py b/tests/unit/cli/verbose_proxy_test.py index f111f8cdbf2..884e3901d35 100644 --- a/tests/unit/cli/verbose_proxy_test.py +++ b/tests/unit/cli/verbose_proxy_test.py @@ -1,8 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - -import six - from compose.cli import verbose_proxy from tests import unittest @@ -10,7 +5,7 @@ class VerboseProxyTestCase(unittest.TestCase): def test_format_call(self): - prefix = '' if six.PY3 else 'u' + prefix = '' if True else 'u' expected = "(%(p)s'arg1', True, key=%(p)s'value')" % dict(p=prefix) actual = verbose_proxy.format_call( ("arg1", True), diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index a7522f939b9..4e5b74843f5 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -1,7 +1,3 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import unicode_literals - import os import shutil import tempfile diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 603eddaf244..b03d3b60ff8 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -1,8 +1,3 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import codecs import os import shutil @@ -3836,7 +3831,7 @@ def test_volume_path_with_non_ascii_directory(self): assert container_path == volume -class MergePathMappingTest(object): +class MergePathMappingTest: config_name = "" def test_empty(self): @@ -3909,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 = [] diff --git a/tests/unit/config/environment_test.py b/tests/unit/config/environment_test.py index 7e394d248f5..6a80ff12254 100644 --- a/tests/unit/config/environment_test.py +++ b/tests/unit/config/environment_test.py @@ -1,8 +1,3 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - import codecs import os import shutil diff --git a/tests/unit/config/interpolation_test.py b/tests/unit/config/interpolation_test.py index 91fc3e69db5..b8b4ba74759 100644 --- a/tests/unit/config/interpolation_test.py +++ b/tests/unit/config/interpolation_test.py @@ -1,7 +1,3 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from compose.config.environment import Environment diff --git a/tests/unit/config/sort_services_test.py b/tests/unit/config/sort_services_test.py index c39ac022562..508c4bba190 100644 --- a/tests/unit/config/sort_services_test.py +++ b/tests/unit/config/sort_services_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from compose.config.errors import DependencyError @@ -8,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 e7cc67b0422..acb8122e6d9 100644 --- a/tests/unit/config/types_test.py +++ b/tests/unit/config/types_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from compose.config.errors import ConfigurationError @@ -42,7 +39,7 @@ def test_parse_extra_hosts_dict(): } -class TestServicePort(object): +class TestServicePort: def test_parse_dict(self): data = { 'target': 8000, @@ -132,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') @@ -219,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/container_test.py b/tests/unit/container_test.py index 626b466d4b3..452475209c5 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import docker from .. import mock diff --git a/tests/unit/network_test.py b/tests/unit/network_test.py index b829de196be..ab7ad59cfbd 100644 --- a/tests/unit/network_test.py +++ b/tests/unit/network_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import pytest from .. import mock diff --git a/tests/unit/parallel_test.py b/tests/unit/parallel_test.py index 0735bfccb48..98412f9a259 100644 --- a/tests/unit/parallel_test.py +++ b/tests/unit/parallel_test.py @@ -1,10 +1,6 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import unittest from threading import Lock -import six from docker.errors import APIError from compose.parallel import GlobalLimit @@ -39,7 +35,7 @@ def test_parallel_execute(self): results, errors = parallel_execute( objects=[1, 2, 3, 4, 5], func=lambda x: x * 2, - get_name=six.text_type, + get_name=str, msg="Doubling", ) @@ -61,7 +57,7 @@ def f(obj): results, errors = parallel_execute( objects=list(range(tasks)), func=f, - get_name=six.text_type, + get_name=str, msg="Testing", limit=limit, ) @@ -85,7 +81,7 @@ def f(obj): results, errors = parallel_execute( objects=list(range(tasks)), func=f, - get_name=six.text_type, + get_name=str, msg="Testing", ) @@ -147,7 +143,7 @@ def test_parallel_execute_alignment(capsys): results, errors = parallel_execute( objects=["short", "a very long name"], func=lambda x: x, - get_name=six.text_type, + get_name=str, msg="Aligning", ) @@ -164,7 +160,7 @@ def test_parallel_execute_ansi(capsys): results, errors = parallel_execute( objects=["something", "something more"], func=lambda x: x, - get_name=six.text_type, + get_name=str, msg="Control characters", ) @@ -180,7 +176,7 @@ def test_parallel_execute_noansi(capsys): results, errors = parallel_execute( objects=["something", "something more"], func=lambda x: x, - get_name=six.text_type, + get_name=str, msg="Control characters", ) diff --git a/tests/unit/progress_stream_test.py b/tests/unit/progress_stream_test.py index 6fdb7d92788..b1365fa928c 100644 --- a/tests/unit/progress_stream_test.py +++ b/tests/unit/progress_stream_test.py @@ -1,14 +1,9 @@ -# ~*~ encoding: utf-8 ~*~ -from __future__ import absolute_import -from __future__ import unicode_literals - import io import os import random import shutil import tempfile - -from six import StringIO +from io import StringIO from compose import progress_stream from tests import unittest diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 6391fac863d..5f27cbe7501 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -1,7 +1,3 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import unicode_literals - import datetime import os import tempfile diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index 592e22f759f..d4e7f3c5d94 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import docker import pytest from docker.constants import DEFAULT_DOCKER_API_VERSION diff --git a/tests/unit/split_buffer_test.py b/tests/unit/split_buffer_test.py index dedd4ee36bb..f1974c83144 100644 --- a/tests/unit/split_buffer_test.py +++ b/tests/unit/split_buffer_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from .. import unittest from compose.utils import split_buffer diff --git a/tests/unit/timeparse_test.py b/tests/unit/timeparse_test.py index 9915932c300..e56595f1893 100644 --- a/tests/unit/timeparse_test.py +++ b/tests/unit/timeparse_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - from compose import timeparse diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index 21b88d962c1..f02dcd696cc 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -1,11 +1,7 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import unicode_literals - from compose import utils -class TestJsonSplitter(object): +class TestJsonSplitter: def test_json_splitter_no_object(self): data = '{"foo": "bar' @@ -20,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'] @@ -33,7 +29,7 @@ def test_stream_with_utf_character(self): assert output == 'ěĝ' -class TestJsonStream(object): +class TestJsonStream: def test_with_falsy_entries(self): stream = [ @@ -62,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 @@ -70,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 457d8558174..0dfbfcd40b7 100644 --- a/tests/unit/volume_test.py +++ b/tests/unit/volume_test.py @@ -1,6 +1,3 @@ -from __future__ import absolute_import -from __future__ import unicode_literals - import docker import pytest @@ -13,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') diff --git a/tox.ini b/tox.ini index 57e57bc635e..90f6e6888fb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py37,pre-commit +envlist = py37,pre-commit [testenv] usedevelop=True