From cf97ca8ee5c4d4f2d2256ad281e144e14fe88804 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 6 Jul 2018 12:46:21 -0400 Subject: [PATCH 1/7] Port majority of src/util --- src/python/pants/util/BUILD | 44 ++++++++++++++++------ src/python/pants/util/collections.py | 2 + src/python/pants/util/contextutil.py | 6 ++- src/python/pants/util/eval.py | 7 ++-- src/python/pants/util/filtering.py | 3 +- src/python/pants/util/netrc.py | 1 + src/python/pants/util/osutil.py | 3 +- src/python/pants/util/retry.py | 1 + src/python/pants/util/rwbuf.py | 1 + src/python/pants/util/s3_log_aggregator.py | 1 + src/python/pants/util/socket.py | 1 + src/python/pants/util/strutil.py | 12 +++--- src/python/pants/util/tarutil.py | 8 ++-- src/python/pants/util/xml_parser.py | 1 + 14 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/python/pants/util/BUILD b/src/python/pants/util/BUILD index fd69c0ee63f..88bc626cbc4 100644 --- a/src/python/pants/util/BUILD +++ b/src/python/pants/util/BUILD @@ -7,19 +7,22 @@ python_library( ) python_library( - name = 'contextutil', - sources = ['contextutil.py'], - dependencies = [ - '3rdparty/python:ansicolors', - '3rdparty/python:six', - ':dirutil', - ':tarutil', - ], + name = 'contextutil', + sources = ['contextutil.py'], + dependencies = [ + '3rdparty/python:ansicolors', + '3rdparty/python:future', + ':dirutil', + ':tarutil', + ], ) python_library( name = 'collections', sources = ['collections.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( @@ -48,7 +51,7 @@ python_library( name = 'eval', sources = ['eval.py'], dependencies = [ - '3rdparty/python:six', + '3rdparty/python:future', ] ) @@ -63,6 +66,9 @@ python_library( python_library( name = 'filtering', sources = ['filtering.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( @@ -78,6 +84,9 @@ python_library( python_library( name = 'netrc', sources = ['netrc.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( @@ -92,6 +101,9 @@ python_library( python_library( name = 'osutil', sources = ['osutil.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( @@ -107,12 +119,16 @@ python_library( python_library( name = 'retry', sources = ['retry.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( name = 'rwbuf', sources = ['rwbuf.py'], dependencies = [ + '3rdparty/python:future', '3rdparty/python:six', ] ) @@ -121,6 +137,7 @@ python_library( name = 's3_log_aggregator', sources = ['s3_log_aggregator.py'], dependencies = [ + '3rdparty/python:future', '3rdparty/python:s3logparse' ] ) @@ -135,14 +152,17 @@ python_binary( python_library( name = 'socket', - sources = ['socket.py'] + sources = ['socket.py'], + dependencies = [ + '3rdparty/python:future', + ] ) python_library( name = 'strutil', sources = ['strutil.py'], dependencies = [ - '3rdparty/python:six', + '3rdparty/python:future', ], ) @@ -150,7 +170,7 @@ python_library( name = 'tarutil', sources = ['tarutil.py'], dependencies = [ - '3rdparty/python:six', + '3rdparty/python:future', ], ) diff --git a/src/python/pants/util/collections.py b/src/python/pants/util/collections.py index d065cafc821..8b06666bca6 100644 --- a/src/python/pants/util/collections.py +++ b/src/python/pants/util/collections.py @@ -5,6 +5,8 @@ from __future__ import (absolute_import, division, generators, nested_scopes, print_function, unicode_literals, with_statement) +from builtins import next + def combined_dict(*dicts): """Combine one or more dicts into a new, unified dict (dicts to the right take precedence).""" diff --git a/src/python/pants/util/contextutil.py b/src/python/pants/util/contextutil.py index f1188958df2..c29b583586f 100644 --- a/src/python/pants/util/contextutil.py +++ b/src/python/pants/util/contextutil.py @@ -14,10 +14,11 @@ import time import uuid import zipfile +from builtins import object, str from contextlib import closing, contextmanager from colors import green -from six import string_types +from future.utils import string_types from pants.util.dirutil import safe_delete from pants.util.tarutil import TarFile @@ -291,7 +292,8 @@ def open_tar(path_or_file, *args, **kwargs): If path_or_file is a file, caller must close it separately. """ (path, fileobj) = ((path_or_file, None) if isinstance(path_or_file, string_types) - else (None, path_or_file)) + else (None, path_or_file)) # TODO(python3port): stop using six.string_types + # This should only accept python3 `str`, not byte strings. with closing(TarFile.open(path, *args, fileobj=fileobj, **kwargs)) as tar: yield tar diff --git a/src/python/pants/util/eval.py b/src/python/pants/util/eval.py index 3f90be1df69..1fd129ef51c 100644 --- a/src/python/pants/util/eval.py +++ b/src/python/pants/util/eval.py @@ -5,9 +5,10 @@ from __future__ import (absolute_import, division, generators, nested_scopes, print_function, unicode_literals, with_statement) +from builtins import range, str from textwrap import dedent -import six +import future def parse_expression(val, acceptable_types, name=None, raise_type=ValueError): @@ -26,7 +27,7 @@ def parse_expression(val, acceptable_types, name=None, raise_type=ValueError): def format_type(typ): return typ.__name__ - if not isinstance(val, six.string_types): + if not isinstance(val, future.utils.string_types): raise raise_type('The raw `val` is not a string. Given {} of type {}.' .format(val, format_type(type(val)))) @@ -35,7 +36,7 @@ def get_name(): def format_raw_value(): lines = val.splitlines() - for line_number in six.moves.range(0, len(lines)): + for line_number in range(0, len(lines)): lines[line_number] = "{line_number:{width}}: {line}".format( line_number=line_number + 1, line=lines[line_number], diff --git a/src/python/pants/util/filtering.py b/src/python/pants/util/filtering.py index ea6b131a0f6..c6a2acf1b5b 100644 --- a/src/python/pants/util/filtering.py +++ b/src/python/pants/util/filtering.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import operator +from builtins import map _identity = lambda x: x @@ -51,7 +52,7 @@ def create_filter(predicate_param, predicate_factory): modifier, param = _extract_modifier(predicate_param) predicates = map(predicate_factory, param.split(',')) def filt(x): - return modifier(any(map(lambda pred: pred(x), predicates))) + return modifier(any(pred(x) for pred in predicates)) return filt diff --git a/src/python/pants/util/netrc.py b/src/python/pants/util/netrc.py index 0e9d2be1eef..5070fb2a094 100644 --- a/src/python/pants/util/netrc.py +++ b/src/python/pants/util/netrc.py @@ -7,6 +7,7 @@ import collections import os +from builtins import object from netrc import netrc as NetrcDb from netrc import NetrcParseError diff --git a/src/python/pants/util/osutil.py b/src/python/pants/util/osutil.py index 8305f58963a..0181b7fee23 100644 --- a/src/python/pants/util/osutil.py +++ b/src/python/pants/util/osutil.py @@ -9,6 +9,7 @@ import os from functools import reduce + logger = logging.getLogger(__name__) @@ -45,7 +46,7 @@ def get_normalized_os_name(): def all_normalized_os_names(): - return OS_ALIASES.keys() + return list(OS_ALIASES.keys()) def known_os_names(): diff --git a/src/python/pants/util/retry.py b/src/python/pants/util/retry.py index 6229c8071ec..1bec1ff9164 100644 --- a/src/python/pants/util/retry.py +++ b/src/python/pants/util/retry.py @@ -7,6 +7,7 @@ import logging import time +from builtins import range logger = logging.getLogger(__name__) diff --git a/src/python/pants/util/rwbuf.py b/src/python/pants/util/rwbuf.py index bb376644269..51c99d09cda 100644 --- a/src/python/pants/util/rwbuf.py +++ b/src/python/pants/util/rwbuf.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import threading +from builtins import object, str from six import StringIO diff --git a/src/python/pants/util/s3_log_aggregator.py b/src/python/pants/util/s3_log_aggregator.py index 40ec294b749..14323cafe72 100644 --- a/src/python/pants/util/s3_log_aggregator.py +++ b/src/python/pants/util/s3_log_aggregator.py @@ -7,6 +7,7 @@ import os import sys +from builtins import object from collections import defaultdict from s3logparse.s3logparse import parse_log_lines diff --git a/src/python/pants/util/socket.py b/src/python/pants/util/socket.py index 05e4fa1a016..8cb99643129 100644 --- a/src/python/pants/util/socket.py +++ b/src/python/pants/util/socket.py @@ -9,6 +9,7 @@ import io import select import socket +from builtins import object def teardown_socket(s): diff --git a/src/python/pants/util/strutil.py b/src/python/pants/util/strutil.py index 2849710ec6b..c3c53780e76 100644 --- a/src/python/pants/util/strutil.py +++ b/src/python/pants/util/strutil.py @@ -8,29 +8,29 @@ import re import shlex -import six +import future def ensure_binary(text_or_binary): - if isinstance(text_or_binary, six.binary_type): + if isinstance(text_or_binary, future.utils.binary_type): return text_or_binary - elif isinstance(text_or_binary, six.text_type): + elif isinstance(text_or_binary, future.utils.text_type): return text_or_binary.encode('utf8') else: raise TypeError('Argument is neither text nor binary type.({})'.format(type(text_or_binary))) def ensure_text(text_or_binary): - if isinstance(text_or_binary, six.binary_type): + if isinstance(text_or_binary, future.utils.binary_type): return text_or_binary.decode('utf-8') - elif isinstance(text_or_binary, six.text_type): + elif isinstance(text_or_binary, future.utils.text_type): return text_or_binary else: raise TypeError('Argument is neither text nor binary type ({})'.format(type(text_or_binary))) def is_text_or_binary(obj): - return isinstance(obj, (six.text_type, six.binary_type)) + return isinstance(obj, (future.utils.text_type, future.utils.binary_type)) def safe_shlex_split(text_or_binary): diff --git a/src/python/pants/util/tarutil.py b/src/python/pants/util/tarutil.py index 7e70ce5c927..7a32ed6742f 100644 --- a/src/python/pants/util/tarutil.py +++ b/src/python/pants/util/tarutil.py @@ -6,13 +6,13 @@ unicode_literals, with_statement) import tarfile +from builtins import str -import six +import future - -if six.PY2: +if future.utils.PY2: class TarFile(tarfile.TarFile): - def next(self): + def __next__(self): """A copy and modification of the next() method in tarfile module. The copy is from tarfile.py of CPython @102457:95df96aa2f5a diff --git a/src/python/pants/util/xml_parser.py b/src/python/pants/util/xml_parser.py index 9e2cc7b4abf..10c9ab1efa0 100644 --- a/src/python/pants/util/xml_parser.py +++ b/src/python/pants/util/xml_parser.py @@ -5,6 +5,7 @@ from __future__ import (absolute_import, division, generators, nested_scopes, print_function, unicode_literals, with_statement) +from builtins import object from xml.dom.minidom import parse From e1daf8977cf0ab69020ed716be41543307ac20e6 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 6 Jul 2018 17:03:58 -0400 Subject: [PATCH 2/7] Fix broken util/filtering --- src/python/pants/util/filtering.py | 3 +-- src/python/pants/util/tarutil.py | 1 + tests/python/pants_test/util/BUILD | 10 ++++++++-- tests/python/pants_test/util/test_collections.py | 1 + tests/python/pants_test/util/test_contextutil.py | 1 + tests/python/pants_test/util/test_dirutil.py | 6 +++--- tests/python/pants_test/util/test_eval.py | 4 ++-- tests/python/pants_test/util/test_memo.py | 1 + tests/python/pants_test/util/test_netrc.py | 1 + tests/python/pants_test/util/test_strutil.py | 1 + 10 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/python/pants/util/filtering.py b/src/python/pants/util/filtering.py index c6a2acf1b5b..787c036b84e 100644 --- a/src/python/pants/util/filtering.py +++ b/src/python/pants/util/filtering.py @@ -6,7 +6,6 @@ unicode_literals, with_statement) import operator -from builtins import map _identity = lambda x: x @@ -50,7 +49,7 @@ def create_filter(predicate_param, predicate_factory): # NOTE: Do not inline this into create_filters above. A separate function is necessary # in order to capture the different closure on each invocation. modifier, param = _extract_modifier(predicate_param) - predicates = map(predicate_factory, param.split(',')) + predicates = [predicate_factory(p) for p in param.split(',')] def filt(x): return modifier(any(pred(x) for pred in predicates)) return filt diff --git a/src/python/pants/util/tarutil.py b/src/python/pants/util/tarutil.py index 7a32ed6742f..dcd5d30874f 100644 --- a/src/python/pants/util/tarutil.py +++ b/src/python/pants/util/tarutil.py @@ -10,6 +10,7 @@ import future + if future.utils.PY2: class TarFile(tarfile.TarFile): def __next__(self): diff --git a/tests/python/pants_test/util/BUILD b/tests/python/pants_test/util/BUILD index f86f45ff348..162e43b4ff5 100644 --- a/tests/python/pants_test/util/BUILD +++ b/tests/python/pants_test/util/BUILD @@ -15,6 +15,7 @@ python_tests( sources = ['test_collections.py'], coverage = ['pants.util.collections'], dependencies = [ + '3rdparty/python:future', 'src/python/pants/util:collections', ] ) @@ -24,6 +25,7 @@ python_tests( sources = ['test_contextutil.py'], coverage = ['pants.util.contextutil'], dependencies = [ + '3rdparty/python:future', '3rdparty/python:mock', 'src/python/pants/util:contextutil', 'src/python/pants/util:process_handler', @@ -35,8 +37,8 @@ python_tests( sources = ['test_dirutil.py'], coverage = ['pants.util.dirutil'], dependencies = [ + '3rdparty/python:future', '3rdparty/python:mock', - '3rdparty/python:six', 'src/python/pants/util:contextutil', 'src/python/pants/util:dirutil', 'src/python/pants/util:objects', @@ -47,7 +49,7 @@ python_tests( name = 'eval', sources = ['test_eval.py'], dependencies = [ - '3rdparty/python:six', + '3rdparty/python:future', 'src/python/pants/util:eval', ] ) @@ -73,6 +75,7 @@ python_tests( name = 'memo', sources = ['test_memo.py'], dependencies = [ + '3rdparty/python:future', 'src/python/pants/util:memo', 'src/python/pants/util:meta', ] @@ -91,6 +94,7 @@ python_tests( name = 'netrc', sources = ['test_netrc.py'], dependencies = [ + '3rdparty/python:future', '3rdparty/python:mock', 'src/python/pants/util:netrc', ] @@ -110,6 +114,7 @@ python_tests( name = 'osutil', sources = ['test_osutil.py'], dependencies = [ + '3rdparty/python:future', 'src/python/pants/util:osutil', 'tests/python/pants_test:test_base', ] @@ -146,6 +151,7 @@ python_tests( name = 'strutil', sources = ['test_strutil.py'], dependencies = [ + '3rdparty/python:future', 'src/python/pants/util:strutil', ] ) diff --git a/tests/python/pants_test/util/test_collections.py b/tests/python/pants_test/util/test_collections.py index 73e91f607ec..e63584f2135 100644 --- a/tests/python/pants_test/util/test_collections.py +++ b/tests/python/pants_test/util/test_collections.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import unittest +from builtins import str from pants.util.collections import assert_single_element, combined_dict, recursively_update diff --git a/tests/python/pants_test/util/test_contextutil.py b/tests/python/pants_test/util/test_contextutil.py index b2ea0193766..df1b1660d73 100644 --- a/tests/python/pants_test/util/test_contextutil.py +++ b/tests/python/pants_test/util/test_contextutil.py @@ -13,6 +13,7 @@ import unittest import uuid import zipfile +from builtins import next, object, range, str from contextlib import contextmanager import mock diff --git a/tests/python/pants_test/util/test_dirutil.py b/tests/python/pants_test/util/test_dirutil.py index 2d73bdca72e..f168d3ef96d 100644 --- a/tests/python/pants_test/util/test_dirutil.py +++ b/tests/python/pants_test/util/test_dirutil.py @@ -11,8 +11,8 @@ import unittest from contextlib import contextmanager +import future import mock -import six from pants.util import dirutil from pants.util.contextutil import pushd, temporary_dir @@ -120,10 +120,10 @@ def test_safe_walk(self): # unicode constructor. with temporary_dir() as tmpdir: safe_mkdir(os.path.join(tmpdir, '中文')) - if isinstance(tmpdir, six.text_type): + if isinstance(tmpdir, future.utils.text_type): tmpdir = tmpdir.encode('utf-8') for _, dirs, _ in dirutil.safe_walk(tmpdir): - self.assertTrue(all(isinstance(dirname, six.text_type) for dirname in dirs)) + self.assertTrue(all(isinstance(dirname, future.utils.text_type) for dirname in dirs)) @contextmanager def tree(self): diff --git a/tests/python/pants_test/util/test_eval.py b/tests/python/pants_test/util/test_eval.py index b3d470bb93b..16ff197b54c 100644 --- a/tests/python/pants_test/util/test_eval.py +++ b/tests/python/pants_test/util/test_eval.py @@ -7,7 +7,7 @@ import unittest -import six +import future from pants.util.eval import parse_expression @@ -15,7 +15,7 @@ class ParseLiteralTest(unittest.TestCase): def test_success_simple(self): - literal = parse_expression("'42'", acceptable_types=six.string_types) + literal = parse_expression("'42'", acceptable_types=future.utils.string_types) self.assertEqual('42', literal) def test_success_mixed(self): diff --git a/tests/python/pants_test/util/test_memo.py b/tests/python/pants_test/util/test_memo.py index e4af7285a0c..cd70390f4e2 100644 --- a/tests/python/pants_test/util/test_memo.py +++ b/tests/python/pants_test/util/test_memo.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import unittest +from builtins import object from pants.util.memo import (memoized, memoized_classmethod, memoized_classproperty, memoized_method, memoized_property, memoized_staticmethod, diff --git a/tests/python/pants_test/util/test_netrc.py b/tests/python/pants_test/util/test_netrc.py index 9344d844bdc..c4d7e94e8f0 100644 --- a/tests/python/pants_test/util/test_netrc.py +++ b/tests/python/pants_test/util/test_netrc.py @@ -7,6 +7,7 @@ import re import unittest +from builtins import str from contextlib import contextmanager from mock import MagicMock, mock_open, patch diff --git a/tests/python/pants_test/util/test_strutil.py b/tests/python/pants_test/util/test_strutil.py index 62a6cb4acdf..458b7ad69f6 100644 --- a/tests/python/pants_test/util/test_strutil.py +++ b/tests/python/pants_test/util/test_strutil.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import unittest +from builtins import bytes from pants.util.strutil import camelcase, ensure_binary, ensure_text, pluralize, strip_prefix From 468627cfabba5b870455044c465a0425a5ff6ac8 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Fri, 6 Jul 2018 17:24:52 -0400 Subject: [PATCH 3/7] Revert util/tarutil port --- src/python/pants/util/BUILD | 2 +- src/python/pants/util/tarutil.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/python/pants/util/BUILD b/src/python/pants/util/BUILD index 88bc626cbc4..6224d6a5a6a 100644 --- a/src/python/pants/util/BUILD +++ b/src/python/pants/util/BUILD @@ -170,7 +170,7 @@ python_library( name = 'tarutil', sources = ['tarutil.py'], dependencies = [ - '3rdparty/python:future', + '3rdparty/python:six', ], ) diff --git a/src/python/pants/util/tarutil.py b/src/python/pants/util/tarutil.py index dcd5d30874f..7e70ce5c927 100644 --- a/src/python/pants/util/tarutil.py +++ b/src/python/pants/util/tarutil.py @@ -6,14 +6,13 @@ unicode_literals, with_statement) import tarfile -from builtins import str -import future +import six -if future.utils.PY2: +if six.PY2: class TarFile(tarfile.TarFile): - def __next__(self): + def next(self): """A copy and modification of the next() method in tarfile module. The copy is from tarfile.py of CPython @102457:95df96aa2f5a From 323b7d012d3e6c790c8898ca0377d841f5d12e1a Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Mon, 9 Jul 2018 11:45:50 -0400 Subject: [PATCH 4/7] Revert util/contextutil overriding str --- src/python/pants/util/contextutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/pants/util/contextutil.py b/src/python/pants/util/contextutil.py index c29b583586f..89b67286210 100644 --- a/src/python/pants/util/contextutil.py +++ b/src/python/pants/util/contextutil.py @@ -14,7 +14,7 @@ import time import uuid import zipfile -from builtins import object, str +from builtins import object from contextlib import closing, contextmanager from colors import green From 6eaf573add817fb2a9f448ba306b57d1e7401bb8 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Mon, 9 Jul 2018 12:10:23 -0400 Subject: [PATCH 5/7] Make temporary_dir() parameter safe by using byte literal. --- src/python/pants/util/contextutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/pants/util/contextutil.py b/src/python/pants/util/contextutil.py index 89b67286210..199b653361a 100644 --- a/src/python/pants/util/contextutil.py +++ b/src/python/pants/util/contextutil.py @@ -156,7 +156,7 @@ def signal_handler_as(sig, handler): @contextmanager -def temporary_dir(root_dir=None, cleanup=True, suffix=str(), permissions=None, prefix=tempfile.template): +def temporary_dir(root_dir=None, cleanup=True, suffix=b'', permissions=None, prefix=tempfile.template): """ A with-context that creates a temporary directory. From 10a3a88e1d7a12db1db1e5ca827f9c50febd1063 Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Mon, 9 Jul 2018 15:15:20 -0400 Subject: [PATCH 6/7] Expect rwbuf to be in bytes --- src/python/pants/util/rwbuf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/python/pants/util/rwbuf.py b/src/python/pants/util/rwbuf.py index 51c99d09cda..33282018943 100644 --- a/src/python/pants/util/rwbuf.py +++ b/src/python/pants/util/rwbuf.py @@ -6,7 +6,7 @@ unicode_literals, with_statement) import threading -from builtins import object, str +from builtins import bytes, object from six import StringIO @@ -35,8 +35,10 @@ def read_from(self, pos, size=-1): return self._io.read() if size == -1 else self._io.read(size) def write(self, s): + if not isinstance(s, bytes): + raise ValueError('Argument not in bytes: {0}'.format(s)) with self._lock: - self.do_write(str(s)) + self.do_write(s) self._io.flush() def flush(self): From b5358abc79337d0a044ca25ddea928ddf7de1c7a Mon Sep 17 00:00:00 2001 From: Eric Arellano Date: Mon, 9 Jul 2018 16:39:34 -0400 Subject: [PATCH 7/7] Improve error message --- src/python/pants/util/rwbuf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/pants/util/rwbuf.py b/src/python/pants/util/rwbuf.py index 33282018943..34a85acd03b 100644 --- a/src/python/pants/util/rwbuf.py +++ b/src/python/pants/util/rwbuf.py @@ -36,7 +36,7 @@ def read_from(self, pos, size=-1): def write(self, s): if not isinstance(s, bytes): - raise ValueError('Argument not in bytes: {0}'.format(s)) + raise ValueError('Expected bytes, not {}, for argument {}'.format(type(s), s)) with self._lock: self.do_write(s) self._io.flush()