This is an automated email from the ASF dual-hosted git repository.

kojiromike pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new a3e4572  AVRO-2921: Type Fixes for avro.codecs (#1260)
a3e4572 is described below

commit a3e4572dc6de4097cdf9d1e9fe984ed59ba281ad
Author: Michael A. Smith <[email protected]>
AuthorDate: Wed Jun 16 19:28:10 2021 -0400

    AVRO-2921: Type Fixes for avro.codecs (#1260)
    
    * AVRO-2921: Fixup Test Coverage
    
    Ensure test coverage is good before adding more type hints.
    
    * AVRO-2921: Type Fixes for avro.codecs
---
 lang/py/avro/codecs.py                       | 86 ++++++++++++++--------------
 lang/py/avro/datafile.py                     |  2 +-
 lang/py/avro/test/gen_interop_data.py        |  2 +-
 lang/py/avro/test/test_bench.py              |  4 +-
 lang/py/avro/test/test_datafile.py           |  2 +-
 lang/py/avro/test/test_init.py               |  2 +-
 lang/py/avro/test/test_io.py                 |  2 +-
 lang/py/avro/test/test_ipc.py                |  2 +-
 lang/py/avro/test/test_protocol.py           | 19 +++---
 lang/py/avro/test/test_schema.py             | 30 ++++------
 lang/py/avro/test/test_script.py             |  2 +-
 lang/py/avro/test/test_tether_task.py        |  2 +-
 lang/py/avro/test/test_tether_task_runner.py | 22 +------
 lang/py/avro/test/test_tether_word_count.py  |  4 +-
 lang/py/avro/tether/tether_task_runner.py    | 20 +++++--
 15 files changed, 92 insertions(+), 109 deletions(-)

diff --git a/lang/py/avro/codecs.py b/lang/py/avro/codecs.py
index fa89d77..e1655fe 100644
--- a/lang/py/avro/codecs.py
+++ b/lang/py/avro/codecs.py
@@ -30,8 +30,8 @@ import abc
 import binascii
 import io
 import struct
-import sys
 import zlib
+from typing import Dict, Tuple, Type
 
 import avro.errors
 import avro.io
@@ -42,6 +42,11 @@ import avro.io
 STRUCT_CRC32 = struct.Struct(">I")  # big-endian unsigned int
 
 
+def _check_crc32(bytes_: bytes, checksum: bytes) -> None:
+    if binascii.crc32(bytes_) & 0xFFFFFFFF != STRUCT_CRC32.unpack(checksum)[0]:
+        raise avro.errors.AvroException("Checksum failure")
+
+
 try:
     import bz2
 
@@ -65,8 +70,9 @@ except ImportError:
 class Codec(abc.ABC):
     """Abstract base class for all Avro codec classes."""
 
+    @staticmethod
     @abc.abstractmethod
-    def compress(self, data):
+    def compress(data: bytes) -> Tuple[bytes, int]:
         """Compress the passed data.
 
         :param data: a byte string to be compressed
@@ -76,8 +82,9 @@ class Codec(abc.ABC):
         :return: compressed data and its length
         """
 
+    @staticmethod
     @abc.abstractmethod
-    def decompress(self, readers_decoder):
+    def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
         """Read compressed data via the passed BinaryDecoder and decompress it.
 
         :param readers_decoder: a BinaryDecoder object currently being used for
@@ -91,22 +98,26 @@ class Codec(abc.ABC):
 
 
 class NullCodec(Codec):
-    def compress(self, data):
+    @staticmethod
+    def compress(data: bytes) -> Tuple[bytes, int]:
         return data, len(data)
 
-    def decompress(self, readers_decoder):
+    @staticmethod
+    def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
         readers_decoder.skip_long()
         return readers_decoder
 
 
 class DeflateCodec(Codec):
-    def compress(self, data):
+    @staticmethod
+    def compress(data: bytes) -> Tuple[bytes, int]:
         # The first two characters and last character are zlib
         # wrappers around deflate data.
         compressed_data = zlib.compress(data)[2:-1]
         return compressed_data, len(compressed_data)
 
-    def decompress(self, readers_decoder):
+    @staticmethod
+    def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
         # Compressed data is stored as (length, data), which
         # corresponds to how the "bytes" type is encoded.
         data = readers_decoder.read_bytes()
@@ -119,11 +130,13 @@ class DeflateCodec(Codec):
 if has_bzip2:
 
     class BZip2Codec(Codec):
-        def compress(self, data):
+        @staticmethod
+        def compress(data: bytes) -> Tuple[bytes, int]:
             compressed_data = bz2.compress(data)
             return compressed_data, len(compressed_data)
 
-        def decompress(self, readers_decoder):
+        @staticmethod
+        def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
             length = readers_decoder.read_long()
             data = readers_decoder.read(length)
             uncompressed = bz2.decompress(data)
@@ -133,35 +146,34 @@ if has_bzip2:
 if has_snappy:
 
     class SnappyCodec(Codec):
-        def compress(self, data):
+        @staticmethod
+        def compress(data: bytes) -> Tuple[bytes, int]:
             compressed_data = snappy.compress(data)
             # A 4-byte, big-endian CRC32 checksum
             compressed_data += STRUCT_CRC32.pack(binascii.crc32(data) & 
0xFFFFFFFF)
             return compressed_data, len(compressed_data)
 
-        def decompress(self, readers_decoder):
+        @staticmethod
+        def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
             # Compressed data includes a 4-byte CRC32 checksum
             length = readers_decoder.read_long()
             data = readers_decoder.read(length - 4)
             uncompressed = snappy.decompress(data)
             checksum = readers_decoder.read(4)
-            self.check_crc32(uncompressed, checksum)
+            _check_crc32(uncompressed, checksum)
             return avro.io.BinaryDecoder(io.BytesIO(uncompressed))
 
-        def check_crc32(self, bytes, checksum):
-            checksum = STRUCT_CRC32.unpack(checksum)[0]
-            if binascii.crc32(bytes) & 0xFFFFFFFF != checksum:
-                raise avro.errors.AvroException("Checksum failure")
-
 
 if has_zstandard:
 
     class ZstandardCodec(Codec):
-        def compress(self, data):
+        @staticmethod
+        def compress(data: bytes) -> Tuple[bytes, int]:
             compressed_data = zstd.ZstdCompressor().compress(data)
             return compressed_data, len(compressed_data)
 
-        def decompress(self, readers_decoder):
+        @staticmethod
+        def decompress(readers_decoder: avro.io.BinaryDecoder) -> 
avro.io.BinaryDecoder:
             length = readers_decoder.read_long()
             data = readers_decoder.read(length)
             uncompressed = bytearray()
@@ -175,27 +187,15 @@ if has_zstandard:
             return avro.io.BinaryDecoder(io.BytesIO(uncompressed))
 
 
-def get_codec(codec_name):
-    codec_name = codec_name.lower()
-    if codec_name == "null":
-        return NullCodec()
-    if codec_name == "deflate":
-        return DeflateCodec()
-    if codec_name == "bzip2" and has_bzip2:
-        return BZip2Codec()
-    if codec_name == "snappy" and has_snappy:
-        return SnappyCodec()
-    if codec_name == "zstandard" and has_zstandard:
-        return ZstandardCodec()
-    raise avro.errors.UnsupportedCodec(f"Unsupported codec: {codec_name}. (Is 
it installed?)")
-
-
-def supported_codec_names():
-    codec_names = ["null", "deflate"]
-    if has_bzip2:
-        codec_names.append("bzip2")
-    if has_snappy:
-        codec_names.append("snappy")
-    if has_zstandard:
-        codec_names.append("zstandard")
-    return codec_names
+KNOWN_CODECS: Dict[str, Type[Codec]] = {
+    name[:-5].lower(): class_
+    for name, class_ in globals().items()
+    if class_ != Codec and name.endswith("Codec") and isinstance(class_, type) 
and issubclass(class_, Codec)
+}
+
+
+def get_codec(codec_name: str) -> Type[Codec]:
+    try:
+        return KNOWN_CODECS[codec_name]
+    except KeyError:
+        raise avro.errors.UnsupportedCodec(f"Unsupported codec: {codec_name}. 
(Is it installed?)")
diff --git a/lang/py/avro/datafile.py b/lang/py/avro/datafile.py
index 8a660bc..b766854 100644
--- a/lang/py/avro/datafile.py
+++ b/lang/py/avro/datafile.py
@@ -53,7 +53,7 @@ META_SCHEMA = avro.schema.parse(
 )
 
 NULL_CODEC = "null"
-VALID_CODECS = avro.codecs.supported_codec_names()
+VALID_CODECS = avro.codecs.KNOWN_CODECS.keys()
 VALID_ENCODINGS = ["binary"]  # not used yet
 
 CODEC_KEY = "avro.codec"
diff --git a/lang/py/avro/test/gen_interop_data.py 
b/lang/py/avro/test/gen_interop_data.py
index 68672ba..73b47ce 100644
--- a/lang/py/avro/test/gen_interop_data.py
+++ b/lang/py/avro/test/gen_interop_data.py
@@ -27,7 +27,7 @@ import avro.io
 import avro.schema
 
 NULL_CODEC = "null"
-CODECS_TO_VALIDATE = avro.codecs.supported_codec_names()
+CODECS_TO_VALIDATE = avro.codecs.KNOWN_CODECS.keys()
 
 DATUM = {
     "intField": 12,
diff --git a/lang/py/avro/test/test_bench.py b/lang/py/avro/test/test_bench.py
index 3bd1689..34c926b 100644
--- a/lang/py/avro/test/test_bench.py
+++ b/lang/py/avro/test/test_bench.py
@@ -52,9 +52,9 @@ MAX_WRITE_SECONDS = 3 if platform.python_implementation() == 
"PyPy" else 1
 MAX_READ_SECONDS = 3 if platform.python_implementation() == "PyPy" else 1
 
 
-try:
+try:  # pragma: no cover
     randbytes = random.randbytes  # type: ignore
-except AttributeError:
+except AttributeError:  # pragma: no cover
 
     def randbytes(n):
         """Polyfill for random.randbytes in Python < 3.9"""
diff --git a/lang/py/avro/test/test_datafile.py 
b/lang/py/avro/test/test_datafile.py
index 5bb0d28..bb98857 100644
--- a/lang/py/avro/test/test_datafile.py
+++ b/lang/py/avro/test/test_datafile.py
@@ -28,7 +28,7 @@ import avro.datafile
 import avro.io
 import avro.schema
 
-CODECS_TO_VALIDATE = avro.codecs.supported_codec_names()
+CODECS_TO_VALIDATE = avro.codecs.KNOWN_CODECS.keys()
 TEST_PAIRS = tuple(
     (avro.schema.parse(schema), datum)
     for schema, datum in (
diff --git a/lang/py/avro/test/test_init.py b/lang/py/avro/test/test_init.py
index edd14c8..e37dc19 100644
--- a/lang/py/avro/test/test_init.py
+++ b/lang/py/avro/test/test_init.py
@@ -28,5 +28,5 @@ class TestVersion(unittest.TestCase):
         self.assertTrue(hasattr(avro, "__version__"))
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_io.py b/lang/py/avro/test/test_io.py
index 973f51d..db4d043 100644
--- a/lang/py/avro/test/test_io.py
+++ b/lang/py/avro/test/test_io.py
@@ -561,5 +561,5 @@ def load_tests(loader, default_tests, pattern):
     return suite
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_ipc.py b/lang/py/avro/test/test_ipc.py
index 59006d7..969988a 100644
--- a/lang/py/avro/test/test_ipc.py
+++ b/lang/py/avro/test/test_ipc.py
@@ -39,5 +39,5 @@ class TestIPC(unittest.TestCase):
         self.assertEqual("/", client_with_default_path.req_resource)
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_protocol.py 
b/lang/py/avro/test/test_protocol.py
index 373abb9..b3cd7bd 100644
--- a/lang/py/avro/test/test_protocol.py
+++ b/lang/py/avro/test/test_protocol.py
@@ -411,17 +411,16 @@ class ProtocolParseTestCase(unittest.TestCase):
         """Parsing a valid protocol should not error."""
         try:
             self.test_proto.parse()
-        except avro.errors.ProtocolParseException:
+        except avro.errors.ProtocolParseException:  # pragma: no coverage
             self.fail(f"Valid protocol failed to parse: {self.test_proto!s}")
 
     def parse_invalid(self):
         """Parsing an invalid protocol should error."""
-        try:
+        with self.assertRaises(
+            (avro.errors.ProtocolParseException, 
avro.errors.SchemaParseException),
+            msg=f"Invalid protocol should not have parsed: 
{self.test_proto!s}",
+        ):
             self.test_proto.parse()
-        except (avro.errors.ProtocolParseException, 
avro.errors.SchemaParseException):
-            pass
-        else:
-            self.fail(f"Invalid protocol should not have parsed: 
{self.test_proto!s}")
 
 
 class ErrorProtocolTestCase(unittest.TestCase):
@@ -439,8 +438,9 @@ class ErrorProtocolTestCase(unittest.TestCase):
     def check_error_protocol_exists(self):
         """Protocol messages should always have at least a string error 
protocol."""
         p = self.test_proto.parse()
-        for k, m in p.messages.items():
-            self.assertIsNotNone(m.errors, f"Message {k} did not have the 
expected implicit string error protocol.")
+        if p.messages is not None:
+            for k, m in p.messages.items():
+                self.assertIsNotNone(m.errors, f"Message {k} did not have the 
expected implicit string error protocol.")
 
 
 class RoundTripParseTestCase(unittest.TestCase):
@@ -468,8 +468,9 @@ def load_tests(loader, default_tests, pattern):
     suite.addTests(loader.loadTestsFromTestCase(TestMisc))
     suite.addTests(ProtocolParseTestCase(ex) for ex in EXAMPLES)
     suite.addTests(RoundTripParseTestCase(ex) for ex in VALID_EXAMPLES)
+    suite.addTests(ErrorProtocolTestCase(ex) for ex in VALID_EXAMPLES)
     return suite
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_schema.py b/lang/py/avro/test/test_schema.py
index 52ecca5..1a8e96a 100644
--- a/lang/py/avro/test/test_schema.py
+++ b/lang/py/avro/test/test_schema.py
@@ -691,18 +691,12 @@ class TestMisc(unittest.TestCase):
     def test_parse_invalid_symbol(self):
         """Disabling enumschema symbol validation should allow invalid symbols 
to pass."""
         test_schema_string = json.dumps({"type": "enum", "name": "AVRO2174", 
"symbols": ["white space"]})
-
-        try:
-            case = avro.schema.parse(test_schema_string, 
validate_enum_symbols=True)
-        except avro.errors.InvalidName:
-            pass
-        else:
-            self.fail("When enum symbol validation is enabled, " "an invalid 
symbol should raise InvalidName.")
-
+        with self.assertRaises(avro.errors.InvalidName, msg="When enum symbol 
validation is enabled, an invalid symbol should raise InvalidName."):
+            avro.schema.parse(test_schema_string, validate_enum_symbols=True)
         try:
-            case = avro.schema.parse(test_schema_string, 
validate_enum_symbols=False)
-        except avro.errors.InvalidName:
-            self.fail("When enum symbol validation is disabled, " "an invalid 
symbol should not raise InvalidName.")
+            avro.schema.parse(test_schema_string, validate_enum_symbols=False)
+        except avro.errors.InvalidName:  # pragma: no coverage
+            self.fail("When enum symbol validation is disabled, an invalid 
symbol should not raise InvalidName.")
 
 
 class SchemaParseTestCase(unittest.TestCase):
@@ -724,7 +718,7 @@ class SchemaParseTestCase(unittest.TestCase):
         with warnings.catch_warnings(record=True) as actual_warnings:
             try:
                 self.test_schema.parse()
-            except (avro.errors.AvroException, 
avro.errors.SchemaParseException):
+            except (avro.errors.AvroException, 
avro.errors.SchemaParseException):  # pragma: no coverage
                 self.fail(f"Valid schema failed to parse: 
{self.test_schema!s}")
             actual_messages = [str(wmsg.message) for wmsg in actual_warnings]
             if self.test_schema.warnings:
@@ -735,12 +729,10 @@ class SchemaParseTestCase(unittest.TestCase):
 
     def parse_invalid(self):
         """Parsing an invalid schema should error."""
-        try:
+        with self.assertRaises(
+            (avro.errors.AvroException, avro.errors.SchemaParseException), 
msg=f"Invalid schema should not have parsed: {self.test_schema!s}"
+        ):
             self.test_schema.parse()
-        except (avro.errors.AvroException, avro.errors.SchemaParseException):
-            pass
-        else:
-            self.fail(f"Invalid schema should not have parsed: 
{self.test_schema!s}")
 
 
 class RoundTripParseTestCase(unittest.TestCase):
@@ -824,7 +816,7 @@ class OtherAttributesTestCase(unittest.TestCase):
         sch = self.test_schema.parse()
         try:
             self.assertNotEqual(sch, object(), "A schema is never equal to a 
non-schema instance.")
-        except AttributeError:
+        except AttributeError:  # pragma: no coverage
             self.fail("Comparing a schema to a non-schema should be False, but 
not error.")
         round_trip = avro.schema.parse(str(sch))
         self.assertEqual(
@@ -1256,5 +1248,5 @@ def load_tests(loader, default_tests, pattern):
     return suite
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_script.py b/lang/py/avro/test/test_script.py
index 32437dc..07fef9b 100644
--- a/lang/py/avro/test/test_script.py
+++ b/lang/py/avro/test/test_script.py
@@ -189,7 +189,7 @@ class TestWrite(unittest.TestCase):
         for filename in (self.csv_file, self.json_file, self.schema_file):
             try:
                 os.unlink(filename)
-            except OSError:
+            except OSError:  # pragma: no coverage
                 continue
 
     def _run(self, *args, **kw):
diff --git a/lang/py/avro/test/test_tether_task.py 
b/lang/py/avro/test/test_tether_task.py
index ca50247..f00251e 100644
--- a/lang/py/avro/test/test_tether_task.py
+++ b/lang/py/avro/test/test_tether_task.py
@@ -111,5 +111,5 @@ class TestTetherTask(unittest.TestCase):
                 proc.kill()
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_tether_task_runner.py 
b/lang/py/avro/test/test_tether_task_runner.py
index 25b7cd2..7696161 100644
--- a/lang/py/avro/test/test_tether_task_runner.py
+++ b/lang/py/avro/test/test_tether_task_runner.py
@@ -43,8 +43,6 @@ class TestTetherTaskRunner(unittest.TestCase):
         proc = None
         try:
             # launch the server in a separate process
-            env = dict()
-            env["PYTHONPATH"] = ":".join(sys.path)
             parent_port = avro.tether.util.find_port()
 
             pyfile = avro.test.mock_tether_parent.__file__
@@ -59,13 +57,6 @@ class TestTetherTaskRunner(unittest.TestCase):
             runner = 
avro.tether.tether_task_runner.TaskRunner(avro.test.word_count_task.WordCountTask())
 
             runner.start(outputport=parent_port, join=False)
-            for _ in range(12):
-                if runner.server is not None:
-                    break
-                time.sleep(1)
-            else:
-                raise RuntimeError("Server never started")
-
             # Test sending various messages to the server and ensuring they 
are processed correctly
             requestor = avro.tether.tether_task.HTTPRequestor(
                 "localhost",
@@ -140,8 +131,6 @@ class TestTetherTaskRunner(unittest.TestCase):
             # shutdown the logging
             logging.shutdown()
 
-        except Exception as e:
-            raise
         finally:
             # close the process
             if not (proc is None):
@@ -158,8 +147,6 @@ class TestTetherTaskRunner(unittest.TestCase):
         runnerproc = None
         try:
             # launch the server in a separate process
-            env = dict()
-            env["PYTHONPATH"] = ":".join(sys.path)
             parent_port = avro.tether.util.find_port()
 
             pyfile = avro.test.mock_tether_parent.__file__
@@ -171,16 +158,13 @@ class TestTetherTaskRunner(unittest.TestCase):
             time.sleep(1)
 
             # start the tether_task_runner in a separate process
-            env = {"AVRO_TETHER_OUTPUT_PORT": f"{parent_port}"}
-            env["PYTHONPATH"] = ":".join(sys.path)
-
             runnerproc = subprocess.Popen(
                 [
                     sys.executable,
                     avro.tether.tether_task_runner.__file__,
                     "avro.test.word_count_task.WordCountTask",
                 ],
-                env=env,
+                env={"AVRO_TETHER_OUTPUT_PORT": f"{parent_port}", 
"PYTHONPATH": ":".join(sys.path)},
             )
 
             # possible race condition wait for the process to start
@@ -191,8 +175,6 @@ class TestTetherTaskRunner(unittest.TestCase):
             # so we give the subprocess time to start up
             time.sleep(1)
 
-        except Exception as e:
-            raise
         finally:
             # close the process
             if not (runnerproc is None):
@@ -202,5 +184,5 @@ class TestTetherTaskRunner(unittest.TestCase):
                 proc.kill()
 
 
-if __name__ == ("__main__"):
+if __name__ == ("__main__"):  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/test/test_tether_word_count.py 
b/lang/py/avro/test/test_tether_word_count.py
index abcac02..5c876ee 100644
--- a/lang/py/avro/test/test_tether_word_count.py
+++ b/lang/py/avro/test/test_tether_word_count.py
@@ -70,7 +70,7 @@ _OUT_SCHEMA = """{
 _PYTHON_PATH = 
os.pathsep.join([os.path.dirname(os.path.dirname(avro.__file__)), 
os.path.dirname(__file__)])
 
 
-def _has_java():
+def _has_java():  # pragma: no coverage
     """Detect if this system has a usable java installed.
 
     On most systems, this is just checking if `java` is in the PATH.
@@ -177,5 +177,5 @@ class TestTetherWordCount(unittest.TestCase):
         self.assertDictEqual(actual_counts, expected_counts)
 
 
-if __name__ == "__main__":
+if __name__ == "__main__":  # pragma: no coverage
     unittest.main()
diff --git a/lang/py/avro/tether/tether_task_runner.py 
b/lang/py/avro/tether/tether_task_runner.py
index ccd5a02..fcd2dfb 100644
--- a/lang/py/avro/tether/tether_task_runner.py
+++ b/lang/py/avro/tether/tether_task_runner.py
@@ -134,8 +134,9 @@ class TaskRunner:
     implements the logic for the mapper and reducer phases
     """
 
-    server = None
+    _server = None
     sthread = None
+    timeout = 12  # number of seconds to wait for server to come up.
 
     def __init__(self, task):
         """
@@ -150,6 +151,14 @@ class TaskRunner:
             raise avro.errors.AvroException("task must be an instance of 
tether task")
         self.task = task
 
+    @property
+    def server(self):
+        for t in range(self.timeout):
+            if self._server:
+                return self._server
+            time.sleep(1)
+        raise RuntimeError("Server never started")
+
     def start(self, outputport=None, join=True):
         """
         Start the server
@@ -170,9 +179,9 @@ class TaskRunner:
         address = ("localhost", port)
 
         def thread_run(task_runner=None):
-            task_runner.server = http.server.HTTPServer(address, 
HTTPHandlerGen(task_runner))
-            task_runner.server.allow_reuse_address = True
-            task_runner.server.serve_forever()
+            task_runner._server = http.server.HTTPServer(address, 
HTTPHandlerGen(task_runner))
+            task_runner._server.allow_reuse_address = True
+            task_runner._server.serve_forever()
 
         # create a separate thread for the http server
         sthread = threading.Thread(target=thread_run, kwargs={"task_runner": 
self})
@@ -185,7 +194,7 @@ class TaskRunner:
         # wait for the other thread to finish
         if join:
             self.task.ready_for_shutdown.wait()
-            self.server.shutdown()
+            self._server.shutdown()
 
             # should we do some kind of check to make sure it exits
             self.log.info("Shutdown the logger")
@@ -196,7 +205,6 @@ class TaskRunner:
         """
         Handler for the close message
         """
-
         self.task.close()
 
 

Reply via email to