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 d5e35e8  AVRO-2893: Isolate Errors to Simplify Import Graph (#933)
d5e35e8 is described below

commit d5e35e8afaee0566cd85c748ffbff3841be2bd20
Author: Michael A. Smith <[email protected]>
AuthorDate: Sat Aug 15 13:50:15 2020 -0400

    AVRO-2893: Isolate Errors to Simplify Import Graph (#933)
    
    * AVRO-2893: Isolate Errors to Simplify Import Graph
    
    If an exception is created in avro.schema and handled in avro.io, then 
another exception can't be created in avro.io and handled in avro.schema.
    
    Isolating the exceptions in their own module makes it easy to reuse code 
between the functional modules themselves.
---
 lang/py/avro/codecs.py                    |  16 ++--
 lang/py/avro/datafile.py                  |  20 +----
 lang/py/avro/errors.py                    |  83 ++++++++++++++++++
 lang/py/avro/io.py                        |  59 ++++---------
 lang/py/avro/ipc.py                       |  47 ++++-------
 lang/py/avro/protocol.py                  |  33 +++-----
 lang/py/avro/schema.py                    | 134 +++++++++++++-----------------
 lang/py/avro/test/mock_tether_parent.py   |   7 +-
 lang/py/avro/test/sample_http_client.py   |  11 +--
 lang/py/avro/test/test_datafile.py        |   8 +-
 lang/py/avro/test/test_io.py              |  26 +++---
 lang/py/avro/test/test_protocol.py        |   5 +-
 lang/py/avro/test/test_schema.py          |  61 +++++++-------
 lang/py/avro/test/txsample_http_client.py |  11 +--
 lang/py/avro/tether/tether_task.py        |  94 +++++++--------------
 lang/py/avro/tether/tether_task_runner.py |  26 +++---
 lang/py/scripts/avro                      |  39 ++++-----
 17 files changed, 317 insertions(+), 363 deletions(-)

diff --git a/lang/py/avro/codecs.py b/lang/py/avro/codecs.py
index 3c0f188..2f3a3b5 100644
--- a/lang/py/avro/codecs.py
+++ b/lang/py/avro/codecs.py
@@ -36,8 +36,8 @@ from abc import ABCMeta, abstractmethod
 from binascii import crc32
 from struct import Struct
 
+import avro.errors
 import avro.io
-from avro.schema import AvroException
 
 #
 # Constants
@@ -152,7 +152,7 @@ if has_snappy:
         def check_crc32(self, bytes, checksum):
             checksum = STRUCT_CRC32.unpack(checksum)[0]
             if crc32(bytes) & 0xffffffff != checksum:
-                raise schema.AvroException("Checksum failure")
+                raise avro.errors.AvroException("Checksum failure")
 
 
 if has_zstandard:
@@ -181,16 +181,16 @@ class Codecs(object):
         codec_name = codec_name.lower()
         if codec_name == "null":
             return NullCodec()
-        elif codec_name == "deflate":
+        if codec_name == "deflate":
             return DeflateCodec()
-        elif codec_name == "bzip2" and has_bzip2:
+        if codec_name == "bzip2" and has_bzip2:
             return BZip2Codec()
-        elif codec_name == "snappy" and has_snappy:
+        if codec_name == "snappy" and has_snappy:
             return SnappyCodec()
-        elif codec_name == "zstandard" and has_zstandard:
+        if codec_name == "zstandard" and has_zstandard:
             return ZstandardCodec()
-        else:
-            raise ValueError("Unsupported codec: %r" % codec_name)
+        raise avro.errors.UnsupportedCodec("Unsupported codec: {}. (Is it 
installed?)"
+                                           .format(codec_name))
 
     @staticmethod
     def supported_codec_names():
diff --git a/lang/py/avro/datafile.py b/lang/py/avro/datafile.py
index 14e332d..3b41660 100644
--- a/lang/py/avro/datafile.py
+++ b/lang/py/avro/datafile.py
@@ -26,6 +26,7 @@ import os
 import random
 import zlib
 
+import avro.errors
 import avro.io
 import avro.schema
 from avro.codecs import Codecs
@@ -54,19 +55,6 @@ CODEC_KEY = "avro.codec"
 SCHEMA_KEY = "avro.schema"
 
 #
-# Exceptions
-#
-
-
-class DataFileException(avro.schema.AvroException):
-    """
-    Raised when there's a problem reading or writing file object containers.
-    """
-
-    def __init__(self, fail_msg):
-        avro.schema.AvroException.__init__(self, fail_msg)
-
-#
 # Write Path
 #
 
@@ -115,7 +103,7 @@ class _DataFile(object):
     def codec(self, value):
         """Meta are stored as bytes, but codec is set as a string."""
         if value not in VALID_CODECS:
-            raise DataFileException("Unknown codec: {!r}".format(value))
+            raise avro.errors.DataFileException("Unknown codec: 
{!r}".format(value))
         self.set_meta(CODEC_KEY, value.encode())
 
     @property
@@ -191,7 +179,7 @@ class DataFileWriter(_DataFile):
     def codec(self, value):
         """Meta are stored as bytes, but codec is set as a string."""
         if value not in VALID_CODECS:
-            raise DataFileException("Unknown codec: {!r}".format(value))
+            raise avro.errors.DataFileException("Unknown codec: 
{!r}".format(value))
         self.set_meta(CODEC_KEY, value.encode())
 
     # TODO(hammer): make a schema for blocks and use datum_writer
@@ -307,7 +295,7 @@ class DataFileReader(_DataFile):
         if header.get('magic') != MAGIC:
             fail_msg = "Not an Avro data file: %s doesn't match %s."\
                        % (header.get('magic'), MAGIC)
-            raise avro.schema.AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
         # set metadata
         self._meta = header['meta']
diff --git a/lang/py/avro/errors.py b/lang/py/avro/errors.py
new file mode 100644
index 0000000..3e54bb1
--- /dev/null
+++ b/lang/py/avro/errors.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+##
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+
+
+class AvroException(Exception):
+    """The base class for exceptions in avro."""
+
+
+class SchemaParseException(AvroException):
+    """Raised when a schema failed to parse."""
+
+
+class InvalidName(SchemaParseException):
+    """User attempted to parse a schema with an invalid name."""
+
+
+class AvroWarning(UserWarning):
+    """Base class for warnings."""
+
+
+class IgnoredLogicalType(AvroWarning):
+    """Warnings for unknown or invalid logical types."""
+
+
+class AvroTypeException(AvroException):
+    """Raised when datum is not an example of schema."""
+    def __init__(self, expected_schema, datum):
+        pretty_expected = json.dumps(json.loads(str(expected_schema)), 
indent=2)
+        fail_msg = "The datum {} is not an example of the schema 
{}".format(datum, pretty_expected)
+        super(AvroTypeException, self).__init__(fail_msg)
+
+
+class SchemaResolutionException(AvroException):
+    def __init__(self, fail_msg, writers_schema=None, readers_schema=None):
+        pretty_writers = json.dumps(json.loads(str(writers_schema)), indent=2)
+        pretty_readers = json.dumps(json.loads(str(readers_schema)), indent=2)
+        if writers_schema:
+            fail_msg += "\nWriter's Schema: {}".format(pretty_writers)
+        if readers_schema:
+            fail_msg += "\nReader's Schema: {}".format(pretty_readers)
+        super(AvroException, self).__init__(fail_msg)
+
+
+class DataFileException(AvroException):
+    """Raised when there's a problem reading or writing file object 
containers."""
+
+
+class AvroRemoteException(AvroException):
+    """Raised when an error message is sent by an Avro requestor or 
responder."""
+
+
+class ConnectionClosedException(AvroException):
+    """Raised when attempting IPC on a closed connection."""
+
+
+class ProtocolParseException(AvroException):
+    """Raised when a protocol failed to parse."""
+
+
+class UnsupportedCodec(NotImplementedError, AvroException):
+    """Raised when the compression named cannot be used."""
+
+
+class UsageError(RuntimeError, AvroException):
+    """An exception raised when incorrect arguments were passed."""
diff --git a/lang/py/avro/io.py b/lang/py/avro/io.py
index e165db3..3ae01d0 100644
--- a/lang/py/avro/io.py
+++ b/lang/py/avro/io.py
@@ -50,7 +50,8 @@ import sys
 from decimal import Decimal, getcontext
 from struct import Struct
 
-from avro import constants, schema, timezones
+import avro.errors
+from avro import constants, timezones
 
 try:
     unicode
@@ -89,30 +90,6 @@ STRUCT_SIGNED_LONG = Struct('>q')     # big-endian signed 
long
 
 
 #
-# Exceptions
-#
-
-class AvroTypeException(schema.AvroException):
-    """Raised when datum is not an example of schema."""
-
-    def __init__(self, expected_schema, datum):
-        pretty_expected = json.dumps(json.loads(str(expected_schema)), 
indent=2)
-        fail_msg = "The datum %s is not an example of the schema %s"\
-                   % (datum, pretty_expected)
-        schema.AvroException.__init__(self, fail_msg)
-
-
-class SchemaResolutionException(schema.AvroException):
-    def __init__(self, fail_msg, writers_schema=None, readers_schema=None):
-        pretty_writers = json.dumps(json.loads(str(writers_schema)), indent=2)
-        pretty_readers = json.dumps(json.loads(str(readers_schema)), indent=2)
-        if writers_schema:
-            fail_msg += "\nWriter's Schema: %s" % pretty_writers
-        if readers_schema:
-            fail_msg += "\nReader's Schema: %s" % pretty_readers
-        schema.AvroException.__init__(self, fail_msg)
-
-#
 # Validate
 #
 
@@ -468,7 +445,7 @@ class BinaryEncoder(object):
         """
         sign, digits, exp = datum.as_tuple()
         if exp > scale:
-            raise AvroTypeException('Scale provided in schema does not match 
the decimal')
+            raise avro.errors.AvroTypeException('Scale provided in schema does 
not match the decimal')
 
         unscaled_datum = 0
         for digit in digits:
@@ -494,7 +471,7 @@ class BinaryEncoder(object):
         """
         sign, digits, exp = datum.as_tuple()
         if exp > scale:
-            raise AvroTypeException('Scale provided in schema does not match 
the decimal')
+            raise avro.errors.AvroTypeException('Scale provided in schema does 
not match the decimal')
 
         unscaled_datum = 0
         for digit in digits:
@@ -629,7 +606,7 @@ class DatumReader(object):
         # schema matching
         if not readers_schema.match(writers_schema):
             fail_msg = 'Schemas do not match.'
-            raise SchemaResolutionException(fail_msg, writers_schema, 
readers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema, readers_schema)
 
         logical_type = getattr(writers_schema, 'logical_type', None)
 
@@ -645,7 +622,7 @@ class DatumReader(object):
 
             # This shouldn't happen because of the match check at the start of 
this method.
             fail_msg = 'Schemas do not match.'
-            raise SchemaResolutionException(fail_msg, writers_schema, 
readers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema, readers_schema)
 
         if writers_schema.type == 'null':
             return decoder.read_null()
@@ -698,7 +675,7 @@ class DatumReader(object):
             return self.read_record(writers_schema, readers_schema, decoder)
         else:
             fail_msg = "Cannot read unknown schema type: %s" % 
writers_schema.type
-            raise schema.AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
     def skip_data(self, writers_schema, decoder):
         if writers_schema.type == 'null':
@@ -731,7 +708,7 @@ class DatumReader(object):
             return self.skip_record(writers_schema, decoder)
         else:
             fail_msg = "Unknown schema type: %s" % writers_schema.type
-            raise schema.AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
     def read_fixed(self, writers_schema, readers_schema, decoder):
         """
@@ -753,13 +730,13 @@ class DatumReader(object):
         if index_of_symbol >= len(writers_schema.symbols):
             fail_msg = "Can't access enum index %d for enum with %d symbols"\
                        % (index_of_symbol, len(writers_schema.symbols))
-            raise SchemaResolutionException(fail_msg, writers_schema, 
readers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema, readers_schema)
         read_symbol = writers_schema.symbols[index_of_symbol]
 
         # schema resolution
         if read_symbol not in readers_schema.symbols:
             fail_msg = "Symbol %s not present in Reader's Schema" % read_symbol
-            raise SchemaResolutionException(fail_msg, writers_schema, 
readers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema, readers_schema)
 
         return read_symbol
 
@@ -855,7 +832,7 @@ class DatumReader(object):
         if index_of_schema >= len(writers_schema.schemas):
             fail_msg = "Can't access branch index %d for union with %d 
branches"\
                        % (index_of_schema, len(writers_schema.schemas))
-            raise SchemaResolutionException(fail_msg, writers_schema, 
readers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema, readers_schema)
         selected_writers_schema = writers_schema.schemas[index_of_schema]
 
         # read data
@@ -866,7 +843,7 @@ class DatumReader(object):
         if index_of_schema >= len(writers_schema.schemas):
             fail_msg = "Can't access branch index %d for union with %d 
branches"\
                        % (index_of_schema, len(writers_schema.schemas))
-            raise SchemaResolutionException(fail_msg, writers_schema)
+            raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema)
         return self.skip_data(writers_schema.schemas[index_of_schema], decoder)
 
     def read_record(self, writers_schema, readers_schema, decoder):
@@ -910,8 +887,8 @@ class DatumReader(object):
                         read_record[field.name] = field_val
                     else:
                         fail_msg = 'No default value for field %s' % field_name
-                        raise SchemaResolutionException(fail_msg, 
writers_schema,
-                                                        readers_schema)
+                        raise avro.errors.SchemaResolutionException(fail_msg, 
writers_schema,
+                                                                    
readers_schema)
         return read_record
 
     def skip_record(self, writers_schema, decoder):
@@ -959,7 +936,7 @@ class DatumReader(object):
             return read_record
         else:
             fail_msg = 'Unknown type: %s' % field_schema.type
-            raise schema.AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
 
 class DatumWriter(object):
@@ -976,7 +953,7 @@ class DatumWriter(object):
 
     def write(self, datum, encoder):
         if not validate(self.writers_schema, datum):
-            raise AvroTypeException(self.writers_schema, datum)
+            raise avro.errors.AvroTypeException(self.writers_schema, datum)
         self.write_data(self.writers_schema, datum, encoder)
 
     def write_data(self, writers_schema, datum, encoder):
@@ -1034,7 +1011,7 @@ class DatumWriter(object):
             self.write_record(writers_schema, datum, encoder)
         else:
             fail_msg = 'Unknown type: %s' % writers_schema.type
-            raise schema.AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
     def write_fixed(self, writers_schema, datum, encoder):
         """
@@ -1106,7 +1083,7 @@ class DatumWriter(object):
             if validate(candidate_schema, datum):
                 index_of_schema = i
         if index_of_schema < 0:
-            raise AvroTypeException(writers_schema, datum)
+            raise avro.errors.AvroTypeException(writers_schema, datum)
 
         # write data
         encoder.write_long(index_of_schema)
diff --git a/lang/py/avro/ipc.py b/lang/py/avro/ipc.py
index 550d696..a52a5af 100644
--- a/lang/py/avro/ipc.py
+++ b/lang/py/avro/ipc.py
@@ -25,13 +25,14 @@ import io
 import os
 from struct import Struct
 
+import avro.errors
 import avro.io
 from avro import protocol, schema
 
 try:
     import httplib  # type: ignore
 except ImportError:
-    import http.client as httplib  # type: ignore
+    from http import client as httplib  # type: ignore
 
 try:
     unicode
@@ -71,23 +72,6 @@ BUFFER_HEADER_LENGTH = 4
 BUFFER_SIZE = 8192
 
 #
-# Exceptions
-#
-
-
-class AvroRemoteException(schema.AvroException):
-    """
-    Raised when an error message is sent by an Avro requestor or responder.
-    """
-
-    def __init__(self, fail_msg=None):
-        schema.AvroException.__init__(self, fail_msg)
-
-
-class ConnectionClosedException(schema.AvroException):
-    pass
-
-#
 # Base IPC Classes (Requestor/Responder)
 #
 
@@ -165,7 +149,7 @@ class BaseRequestor(object):
         # message name
         message = self.local_protocol.messages.get(message_name)
         if message is None:
-            raise schema.AvroException('Unknown message: %s' % message_name)
+            raise avro.errors.AvroException('Unknown message: %s' % 
message_name)
         encoder.write_utf8(message.name)
 
         # message parameters
@@ -183,7 +167,7 @@ class BaseRequestor(object):
             return True
         elif match == 'CLIENT':
             if self.send_protocol:
-                raise schema.AvroException('Handshake failure.')
+                raise avro.errors.AvroException('Handshake failure.')
             self.remote_protocol = protocol.parse(
                 handshake_response.get('serverProtocol'))
             self.remote_hash = handshake_response.get('serverHash')
@@ -191,14 +175,14 @@ class BaseRequestor(object):
             return True
         elif match == 'NONE':
             if self.send_protocol:
-                raise schema.AvroException('Handshake failure.')
+                raise avro.errors.AvroException('Handshake failure.')
             self.remote_protocol = protocol.parse(
                 handshake_response.get('serverProtocol'))
             self.remote_hash = handshake_response.get('serverHash')
             self.send_protocol = True
             return False
         else:
-            raise schema.AvroException('Unexpected match: %s' % match)
+            raise avro.errors.AvroException('Unexpected match: %s' % match)
 
     def read_call_response(self, message_name, decoder):
         """
@@ -216,12 +200,12 @@ class BaseRequestor(object):
         # remote response schema
         remote_message_schema = self.remote_protocol.messages.get(message_name)
         if remote_message_schema is None:
-            raise schema.AvroException('Unknown remote message: %s' % 
message_name)
+            raise avro.errors.AvroException('Unknown remote message: %s' % 
message_name)
 
         # local response schema
         local_message_schema = self.local_protocol.messages.get(message_name)
         if local_message_schema is None:
-            raise schema.AvroException('Unknown local message: %s' % 
message_name)
+            raise avro.errors.AvroException('Unknown local message: %s' % 
message_name)
 
         # error flag
         if not decoder.read_boolean():
@@ -231,17 +215,14 @@ class BaseRequestor(object):
         else:
             writers_schema = remote_message_schema.errors
             readers_schema = local_message_schema.errors
-            raise self.read_error(writers_schema, readers_schema, decoder)
+            datum_reader = avro.io.DatumReader(writers_schema, readers_schema)
+            raise avro.errors.AvroRemoteException(datum_reader.read(decoder))
 
     def read_response(self, writers_schema, readers_schema, decoder):
         datum_reader = avro.io.DatumReader(writers_schema, readers_schema)
         result = datum_reader.read(decoder)
         return result
 
-    def read_error(self, writers_schema, readers_schema, decoder):
-        datum_reader = avro.io.DatumReader(writers_schema, readers_schema)
-        return AvroRemoteException(datum_reader.read(decoder))
-
 
 class Requestor(BaseRequestor):
 
@@ -304,11 +285,11 @@ class Responder(object):
             remote_message = remote_protocol.messages.get(remote_message_name)
             if remote_message is None:
                 fail_msg = 'Unknown remote message: %s' % remote_message_name
-                raise schema.AvroException(fail_msg)
+                raise avro.errors.AvroException(fail_msg)
             local_message = 
self.local_protocol.messages.get(remote_message_name)
             if local_message is None:
                 fail_msg = 'Unknown local message: %s' % remote_message_name
-                raise schema.AvroException(fail_msg)
+                raise avro.errors.AvroException(fail_msg)
             writers_schema = remote_message.request
             readers_schema = local_message.request
             request = self.read_request(writers_schema, readers_schema,
@@ -413,14 +394,14 @@ class FramedReader(object):
             while buffer.tell() < buffer_length:
                 chunk = self.reader.read(buffer_length - buffer.tell())
                 if chunk == '':
-                    raise ConnectionClosedException("Reader read 0 bytes.")
+                    raise avro.errors.ConnectionClosedException("Reader read 0 
bytes.")
                 buffer.write(chunk)
             message.append(buffer.getvalue())
 
     def _read_buffer_length(self):
         read = self.reader.read(BUFFER_HEADER_LENGTH)
         if read == '':
-            raise ConnectionClosedException("Reader read 0 bytes.")
+            raise avro.errors.ConnectionClosedException("Reader read 0 bytes.")
         return BIG_ENDIAN_INT_STRUCT.unpack(read)[0]
 
 
diff --git a/lang/py/avro/protocol.py b/lang/py/avro/protocol.py
index 6bcd9b8..09ecde2 100644
--- a/lang/py/avro/protocol.py
+++ b/lang/py/avro/protocol.py
@@ -24,6 +24,7 @@ from __future__ import absolute_import, division, 
print_function
 import hashlib
 import json
 
+import avro.errors
 import avro.schema
 
 try:
@@ -44,14 +45,6 @@ except NameError:
 VALID_TYPE_SCHEMA_TYPES = ('enum', 'record', 'error', 'fixed')
 
 #
-# Exceptions
-#
-
-
-class ProtocolParseException(avro.schema.AvroException):
-    pass
-
-#
 # Base Classes
 #
 
@@ -65,7 +58,7 @@ class Protocol(object):
             type_object = avro.schema.make_avsc_object(type, type_names)
             if type_object.type not in VALID_TYPE_SCHEMA_TYPES:
                 fail_msg = 'Type %s not an enum, fixed, record, or error.' % 
type
-                raise ProtocolParseException(fail_msg)
+                raise avro.errors.ProtocolParseException(fail_msg)
             type_objects.append(type_object)
         return type_objects
 
@@ -74,14 +67,14 @@ class Protocol(object):
         for name, body in messages.items():
             if name in message_objects:
                 fail_msg = 'Message name "%s" repeated.' % name
-                raise ProtocolParseException(fail_msg)
+                raise avro.errors.ProtocolParseException(fail_msg)
             try:
                 request = body.get('request')
                 response = body.get('response')
                 errors = body.get('errors')
             except AttributeError:
                 fail_msg = 'Message name "%s" has non-object body %s.' % 
(name, body)
-                raise ProtocolParseException(fail_msg)
+                raise avro.errors.ProtocolParseException(fail_msg)
             message_objects[name] = Message(name, request, response, errors, 
names)
         return message_objects
 
@@ -89,19 +82,19 @@ class Protocol(object):
         # Ensure valid ctor args
         if not name:
             fail_msg = 'Protocols must have a non-empty name.'
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         elif not isinstance(name, basestring):
             fail_msg = 'The name property must be a string.'
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         elif not (namespace is None or isinstance(namespace, basestring)):
             fail_msg = 'The namespace property must be a string.'
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         elif not (types is None or isinstance(types, list)):
             fail_msg = 'The types property must be a list.'
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         elif not (messages is None or callable(getattr(messages, 'get', 
None))):
             fail_msg = 'The messages property must be a JSON object.'
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
 
         self._props = {}
         self.set_prop('name', name)
@@ -184,7 +177,7 @@ class Message(object):
     def _parse_request(self, request, names):
         if not isinstance(request, list):
             fail_msg = 'Request property not a list: %s' % request
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         return avro.schema.RecordSchema(None, None, request, names, 'request')
 
     def _parse_response(self, response, names):
@@ -196,7 +189,7 @@ class Message(object):
     def _parse_errors(self, errors, names):
         if not isinstance(errors, list):
             fail_msg = 'Errors property not a list: %s' % errors
-            raise ProtocolParseException(fail_msg)
+            raise avro.errors.ProtocolParseException(fail_msg)
         errors_for_parsing = {'type': 'error_union', 'declared_errors': errors}
         return avro.schema.make_avsc_object(errors_for_parsing, names)
 
@@ -247,7 +240,7 @@ def make_avpr_object(json_data):
         types = json_data.get('types')
         messages = json_data.get('messages')
     except AttributeError:
-        raise ProtocolParseException('Not a JSON object: %s' % json_data)
+        raise avro.errors.ProtocolParseException('Not a JSON object: %s' % 
json_data)
     return Protocol(name, namespace, types, messages)
 
 
@@ -256,7 +249,7 @@ def parse(json_string):
     try:
         json_data = json.loads(json_string)
     except ValueError:
-        raise ProtocolParseException('Error parsing JSON: %s' % json_string)
+        raise avro.errors.ProtocolParseException('Error parsing JSON: %s' % 
json_string)
 
     # construct the Avro Protocol object
     return make_avpr_object(json_data)
diff --git a/lang/py/avro/schema.py b/lang/py/avro/schema.py
index eaada36..913d798 100644
--- a/lang/py/avro/schema.py
+++ b/lang/py/avro/schema.py
@@ -46,6 +46,7 @@ import re
 import sys
 import warnings
 
+import avro.errors
 from avro import constants
 
 try:
@@ -119,37 +120,14 @@ VALID_FIELD_SORT_ORDERS = (
     'ignore',
 )
 
-#
-# Exceptions
-#
-
-
-class AvroException(Exception):
-    pass
-
-
-class SchemaParseException(AvroException):
-    pass
-
-
-class InvalidName(SchemaParseException):
-    """User attempted to parse a schema with an invalid name."""
-
-
-class AvroWarning(UserWarning):
-    """Base class for warnings."""
-
-
-class IgnoredLogicalType(AvroWarning):
-    """Warnings for unknown or invalid logical types."""
-
 
 def validate_basename(basename):
     """Raise InvalidName if the given basename is not a valid name."""
     if not _BASE_NAME_PATTERN.search(basename):
-        raise InvalidName("{!s} is not a valid Avro name because it "
-                          "does not match the pattern {!s}".format(
-                              basename, _BASE_NAME_PATTERN.pattern))
+        raise avro.errors.InvalidName(
+                "{!s} is not a valid Avro name because it "
+                "does not match the pattern {!s}".format(
+                    basename, _BASE_NAME_PATTERN.pattern))
 
 #
 # Base Classes
@@ -164,10 +142,10 @@ class Schema(object):
         # Ensure valid ctor args
         if not isinstance(type, basestring):
             fail_msg = 'Schema type must be a string.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif type not in VALID_TYPES:
             fail_msg = '%s is not a valid type.' % type
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         # add members
         if self._props is None:
@@ -176,13 +154,14 @@ class Schema(object):
         self.type = type
         self._props.update(other_props or {})
 
-    # Read-only properties dict. Printing schemas
-    # creates JSON properties directly from this dict.
-    props = property(lambda self: self._props)
+    @property
+    def props(self):
+        return self._props
 
-    # Read-only property dict. Non-reserved properties
-    other_props = property(lambda self: get_other_props(self._props, 
SCHEMA_RESERVED_PROPS),
-                           doc="dictionary of non-reserved properties")
+    @property
+    def other_props(self):
+        """Dictionary of non-reserved properties"""
+        return get_other_props(self.props, SCHEMA_RESERVED_PROPS)
 
     def check_props(self, other, props):
         """Check that the given props are identical in two schemas.
@@ -219,7 +198,7 @@ class Schema(object):
         be aware of not re-defining schemas that are already listed
         in the parameter names.
         """
-        raise Exception("Must be implemented by subclasses.")
+        raise NotImplemented("Must be implemented by subclasses.")
 
 
 class Name(object):
@@ -251,7 +230,7 @@ class Name(object):
         if name_attr is None:
             return
         if name_attr == "":
-            raise SchemaParseException('Name must not be the empty string.')
+            raise avro.errors.SchemaParseException('Name must not be the empty 
string.')
 
         if '.' in name_attr or space_attr == "" or not (space_attr or 
default_space):
             # The empty string may be used as a namespace to indicate the null 
namespace.
@@ -332,10 +311,10 @@ class Names(object):
 
         if to_add.fullname in VALID_TYPES:
             fail_msg = '%s is a reserved type name.' % to_add.fullname
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif to_add.fullname in self.names:
             fail_msg = 'The name "%s" is already in use.' % to_add.fullname
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         self.names[to_add.fullname] = new_schema
         return to_add
@@ -348,13 +327,13 @@ class NamedSchema(Schema):
         # Ensure valid ctor args
         if not name:
             fail_msg = 'Named Schemas must have a non-empty name.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif not isinstance(name, basestring):
             fail_msg = 'The name property must be a string.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif namespace is not None and not isinstance(namespace, basestring):
             fail_msg = 'The namespace property must be a string.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         # Call parent ctor
         Schema.__init__(self, type, other_props)
@@ -395,20 +374,21 @@ class LogicalSchema(object):
 class DecimalLogicalSchema(LogicalSchema):
     def __init__(self, precision, scale=0, max_precision=0):
         if not isinstance(precision, int) or precision <= 0:
-            raise IgnoredLogicalType(
+            raise avro.errors.IgnoredLogicalType(
                 "Invalid decimal precision {}. Must be a positive 
integer.".format(precision))
 
         if precision > max_precision:
-            raise IgnoredLogicalType(
+            raise avro.errors.IgnoredLogicalType(
                 "Invalid decimal precision {}. Max is {}.".format(precision, 
max_precision))
 
         if not isinstance(scale, int) or scale < 0:
-            raise IgnoredLogicalType(
+            raise avro.errors.IgnoredLogicalType(
                 "Invalid decimal scale {}. Must be a positive 
integer.".format(scale))
 
         if scale > precision:
-            raise IgnoredLogicalType("Invalid decimal scale {}. Cannot be 
greater than precision {}."
-                                     .format(scale, precision))
+            raise avro.errors.IgnoredLogicalType(
+                    "Invalid decimal scale {}. Cannot be greater than 
precision {}.".format(
+                        scale, precision))
 
         super(DecimalLogicalSchema, self).__init__('decimal')
 
@@ -419,13 +399,13 @@ class Field(object):
         # Ensure valid ctor args
         if not name:
             fail_msg = 'Fields must have a non-empty name.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif not isinstance(name, basestring):
             fail_msg = 'The name property must be a string.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif order is not None and order not in VALID_FIELD_SORT_ORDERS:
             fail_msg = 'The order property %s is not valid.' % order
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         # add members
         self._props = {}
@@ -440,7 +420,7 @@ class Field(object):
                 type_schema = make_avsc_object(type, names)
             except Exception as e:
                 fail_msg = 'Type property "%s" not a valid Avro schema: %s' % 
(type, e)
-                raise SchemaParseException(fail_msg)
+                raise avro.errors.SchemaParseException(fail_msg)
         self.set_prop('type', type_schema)
         self.set_prop('name', name)
         self.type = type_schema
@@ -496,7 +476,7 @@ class PrimitiveSchema(Schema):
     def __init__(self, type, other_props=None):
         # Ensure valid ctor args
         if type not in PRIMITIVE_TYPES:
-            raise AvroException("%s is not a valid primitive type." % type)
+            raise avro.errors.AvroException("%s is not a valid primitive 
type." % type)
 
         # Call parent ctor
         Schema.__init__(self, type, other_props=other_props)
@@ -555,7 +535,7 @@ class FixedSchema(NamedSchema):
         # Ensure valid ctor args
         if not isinstance(size, int) or size < 0:
             fail_msg = 'Fixed Schema requires a valid positive integer for 
size property.'
-            raise AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
         # Call parent ctor
         NamedSchema.__init__(self, 'fixed', name, namespace, names, 
other_props)
@@ -619,12 +599,12 @@ class EnumSchema(NamedSchema):
             for symbol in symbols:
                 try:
                     validate_basename(symbol)
-                except InvalidName:
-                    raise InvalidName("An enum symbol must be a valid schema 
name.")
+                except avro.errors.InvalidName:
+                    raise avro.errors.InvalidName("An enum symbol must be a 
valid schema name.")
 
         if len(set(symbols)) < len(symbols):
             fail_msg = 'Duplicate symbol: %s' % symbols
-            raise AvroException(fail_msg)
+            raise avro.errors.AvroException(fail_msg)
 
         # Call parent ctor
         NamedSchema.__init__(self, 'enum', name, namespace, names, other_props)
@@ -676,7 +656,7 @@ class ArraySchema(Schema):
                 items_schema = make_avsc_object(items, names)
             except SchemaParseException as e:
                 fail_msg = 'Items schema (%s) not a valid Avro schema: %s 
(known names: %s)' % (items, e, names.names.keys())
-                raise SchemaParseException(fail_msg)
+                raise avro.errors.SchemaParseException(fail_msg)
 
         self.set_prop('items', items_schema)
 
@@ -718,7 +698,7 @@ class MapSchema(Schema):
             except SchemaParseException:
                 raise
             except Exception:
-                raise SchemaParseException('Values schema is not a valid Avro 
schema.')
+                raise avro.errors.SchemaParseException('Values schema is not a 
valid Avro schema.')
 
         self.set_prop('values', values_schema)
 
@@ -754,7 +734,7 @@ class UnionSchema(Schema):
         # Ensure valid ctor args
         if not isinstance(schemas, list):
             fail_msg = 'Union schema requires a list of schemas.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         # Call parent ctor
         Schema.__init__(self, 'union')
@@ -768,13 +748,13 @@ class UnionSchema(Schema):
                 try:
                     new_schema = make_avsc_object(schema, names)
                 except Exception as e:
-                    raise SchemaParseException('Union item must be a valid 
Avro schema: %s' % str(e))
+                    raise avro.errors.SchemaParseException('Union item must be 
a valid Avro schema: %s' % str(e))
             # check the new schema
             if (new_schema.type in VALID_TYPES and new_schema.type not in 
NAMED_TYPES and
                     new_schema.type in [schema.type for schema in 
schema_objects]):
-                raise SchemaParseException('%s type already in Union' % 
new_schema.type)
+                raise avro.errors.SchemaParseException('%s type already in 
Union' % new_schema.type)
             elif new_schema.type == 'union':
-                raise SchemaParseException('Unions cannot contain other 
unions.')
+                raise avro.errors.SchemaParseException('Unions cannot contain 
other unions.')
             else:
                 schema_objects.append(new_schema)
         self._schemas = schema_objects
@@ -846,10 +826,10 @@ class RecordSchema(NamedSchema):
                 # make sure field name has not been used yet
                 if new_field.name in field_names:
                     fail_msg = 'Field name %s already in use.' % new_field.name
-                    raise SchemaParseException(fail_msg)
+                    raise avro.errors.SchemaParseException(fail_msg)
                 field_names.append(new_field.name)
             else:
-                raise SchemaParseException('Not a valid field: %s' % field)
+                raise avro.errors.SchemaParseException('Not a valid field: %s' 
% field)
             field_objects.append(new_field)
         return field_objects
 
@@ -866,10 +846,10 @@ class RecordSchema(NamedSchema):
         # Ensure valid ctor args
         if fields is None:
             fail_msg = 'Record schema requires a non-empty fields property.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
         elif not isinstance(fields, list):
             fail_msg = 'Fields property must be a list of Avro schemas.'
-            raise SchemaParseException(fail_msg)
+            raise avro.errors.SchemaParseException(fail_msg)
 
         # Call parent ctor (adds own name to namespace, too)
         if schema_type == 'request':
@@ -1042,11 +1022,11 @@ def make_logical_schema(logical_type, type_, 
other_props):
         expected_types = sorted(literal_type for lt, literal_type in 
logical_types if lt == logical_type)
         if expected_types:
             warnings.warn(
-                IgnoredLogicalType("Logical type {} requires literal type {}, 
not {}.".format(
+                avro.errors.IgnoredLogicalType("Logical type {} requires 
literal type {}, not {}.".format(
                     logical_type, "/".join(expected_types), type_)))
         else:
-            warnings.warn(IgnoredLogicalType("Unknown {}, using 
{}.".format(logical_type, type_)))
-    except IgnoredLogicalType as warning:
+            warnings.warn(avro.errors.IgnoredLogicalType("Unknown {}, using 
{}.".format(logical_type, type_)))
+    except avro.errors.IgnoredLogicalType as warning:
         warnings.warn(warning)
     return None
 
@@ -1080,7 +1060,7 @@ def make_avsc_object(json_data, names=None, 
validate_enum_symbols=True):
                     scale = 0 if json_data.get('scale') is None else 
json_data.get('scale')
                     try:
                         return FixedDecimalSchema(size, name, precision, 
scale, namespace, names, other_props)
-                    except IgnoredLogicalType as warning:
+                    except avro.errors.IgnoredLogicalType as warning:
                         warnings.warn(warning)
                 return FixedSchema(name, namespace, size, names, other_props)
             elif type == 'enum':
@@ -1092,7 +1072,7 @@ def make_avsc_object(json_data, names=None, 
validate_enum_symbols=True):
                 doc = json_data.get('doc')
                 return RecordSchema(name, namespace, fields, names, type, doc, 
other_props)
             else:
-                raise SchemaParseException('Unknown Named Type: %s' % type)
+                raise avro.errors.SchemaParseException('Unknown Named Type: 
%s' % type)
         if type in PRIMITIVE_TYPES:
             return PrimitiveSchema(type, other_props)
         if type in VALID_TYPES:
@@ -1106,11 +1086,11 @@ def make_avsc_object(json_data, names=None, 
validate_enum_symbols=True):
                 declared_errors = json_data.get('declared_errors')
                 return ErrorUnionSchema(declared_errors, names)
             else:
-                raise SchemaParseException('Unknown Valid Type: %s' % type)
+                raise avro.errors.SchemaParseException('Unknown Valid Type: 
%s' % type)
         elif type is None:
-            raise SchemaParseException('No "type" property: %s' % json_data)
+            raise avro.errors.SchemaParseException('No "type" property: %s' % 
json_data)
         else:
-            raise SchemaParseException('Undefined type: %s' % type)
+            raise avro.errors.SchemaParseException('Undefined type: %s' % type)
     # JSON array (union)
     elif isinstance(json_data, list):
         return UnionSchema(json_data, names)
@@ -1120,7 +1100,7 @@ def make_avsc_object(json_data, names=None, 
validate_enum_symbols=True):
     # not for us!
     else:
         fail_msg = "Could not make an Avro Schema object from %s." % json_data
-        raise SchemaParseException(fail_msg)
+        raise avro.errors.SchemaParseException(fail_msg)
 
 # TODO(hammer): make method for reading from a file?
 
@@ -1128,14 +1108,16 @@ def make_avsc_object(json_data, names=None, 
validate_enum_symbols=True):
 def parse(json_string, validate_enum_symbols=True):
     """Constructs the Schema from the JSON text.
 
+    @arg json_string: The json string of the schema to parse
     @arg validate_enum_symbols: If False, will allow enum symbols that are not 
valid Avro names.
+    @return Schema
     """
     # parse the JSON
     try:
         json_data = json.loads(json_string)
     except Exception as e:
         msg = 'Error parsing JSON: {}, error = {}'.format(json_string, e)
-        new_exception = SchemaParseException(msg)
+        new_exception = avro.errors.SchemaParseException(msg)
         traceback = sys.exc_info()[2]
         if not hasattr(new_exception, 'with_traceback'):
             raise (new_exception, None, traceback)  # Python 2 syntax
diff --git a/lang/py/avro/test/mock_tether_parent.py 
b/lang/py/avro/test/mock_tether_parent.py
index 710862b..cc33bca 100644
--- a/lang/py/avro/test/mock_tether_parent.py
+++ b/lang/py/avro/test/mock_tether_parent.py
@@ -22,6 +22,7 @@ from __future__ import absolute_import, division, 
print_function
 import socket
 import sys
 
+import avro.errors
 import avro.tether.tether_task
 import avro.tether.util
 from avro import ipc, protocol
@@ -29,7 +30,7 @@ from avro import ipc, protocol
 try:
     import BaseHTTPServer as http_server  # type: ignore
 except ImportError:
-    import http.server as http_server  # type: ignore
+    from http import server as http_server  # type: ignore
 
 
 SERVER_ADDRESS = ('localhost', avro.tether.util.find_port())
@@ -78,14 +79,14 @@ class MockParentHandler(http_server.BaseHTTPRequestHandler):
 
 if __name__ == '__main__':
     if (len(sys.argv) <= 1):
-        raise ValueError("Usage: mock_tether_parent command")
+        raise avro.errors.UsageError("Usage: mock_tether_parent command")
 
     cmd = sys.argv[1].lower()
     if (sys.argv[1] == 'start_server'):
         if (len(sys.argv) == 3):
             port = int(sys.argv[2])
         else:
-            raise ValueError("Usage: mock_tether_parent start_server port")
+            raise avro.errors.UsageError("Usage: mock_tether_parent 
start_server port")
 
         SERVER_ADDRESS = (SERVER_ADDRESS[0], port)
         print("mock_tether_parent: Launching Server on Port: 
{0}".format(SERVER_ADDRESS[1]))
diff --git a/lang/py/avro/test/sample_http_client.py 
b/lang/py/avro/test/sample_http_client.py
index 4e9b881..819a9d3 100644
--- a/lang/py/avro/test/sample_http_client.py
+++ b/lang/py/avro/test/sample_http_client.py
@@ -22,6 +22,7 @@ from __future__ import absolute_import, division, 
print_function
 
 import sys
 
+import avro.errors
 from avro import ipc, protocol
 
 MAIL_PROTOCOL_JSON = """\
@@ -55,14 +56,6 @@ SERVER_HOST = 'localhost'
 SERVER_PORT = 9090
 
 
-class UsageError(Exception):
-    def __init__(self, value):
-        self.value = value
-
-    def __str__(self):
-        return repr(self.value)
-
-
 def make_requestor(server_host, server_port, protocol):
     client = ipc.HTTPTransceiver(SERVER_HOST, SERVER_PORT)
     return ipc.Requestor(protocol, client)
@@ -70,7 +63,7 @@ def make_requestor(server_host, server_port, protocol):
 
 if __name__ == '__main__':
     if len(sys.argv) not in [4, 5]:
-        raise UsageError("Usage: <to> <from> <body> [<count>]")
+        raise avro.errors.UsageError("Usage: <to> <from> <body> [<count>]")
 
     # client code - attach to the server and send a message
     # fill in the Message record
diff --git a/lang/py/avro/test/test_datafile.py 
b/lang/py/avro/test/test_datafile.py
index 924a949..174acb7 100644
--- a/lang/py/avro/test/test_datafile.py
+++ b/lang/py/avro/test/test_datafile.py
@@ -110,7 +110,7 @@ class TestDataFile(unittest.TestCase):
                 print('Correct Round Trip: %s' % is_correct)
                 print('')
         os.remove(FILENAME)
-        self.assertEquals(correct, len(CODECS_TO_VALIDATE) * 
len(SCHEMAS_TO_VALIDATE))
+        self.assertEqual(correct, len(CODECS_TO_VALIDATE) * 
len(SCHEMAS_TO_VALIDATE))
 
     def test_append(self):
         print('')
@@ -159,7 +159,7 @@ class TestDataFile(unittest.TestCase):
                 print('Correct Appended: %s' % is_correct)
                 print('')
         os.remove(FILENAME)
-        self.assertEquals(correct, len(CODECS_TO_VALIDATE) * 
len(SCHEMAS_TO_VALIDATE))
+        self.assertEqual(correct, len(CODECS_TO_VALIDATE) * 
len(SCHEMAS_TO_VALIDATE))
 
     def test_context_manager(self):
         """Test the writer with a 'with' statement."""
@@ -197,8 +197,8 @@ class TestDataFile(unittest.TestCase):
         reader = open(FILENAME, 'rb')
         datum_reader = io.DatumReader()
         with datafile.DataFileReader(reader, datum_reader) as dfr:
-            self.assertEquals(b'foo', dfr.get_meta('test.string'))
-            self.assertEquals(b'1', dfr.get_meta('test.number'))
+            self.assertEqual(b'foo', dfr.get_meta('test.string'))
+            self.assertEqual(b'1', dfr.get_meta('test.number'))
             for datum in dfr:
                 datums.append(datum)
         self.assertTrue(reader.closed)
diff --git a/lang/py/avro/test/test_io.py b/lang/py/avro/test/test_io.py
index 0a93db2..bc3b3c6 100644
--- a/lang/py/avro/test/test_io.py
+++ b/lang/py/avro/test/test_io.py
@@ -244,7 +244,7 @@ class TestIO(unittest.TestCase):
             print('Valid: %s' % validated)
             if validated:
                 passed += 1
-        self.assertEquals(passed, len(SCHEMAS_TO_VALIDATE))
+        self.assertEqual(passed, len(SCHEMAS_TO_VALIDATE))
 
     def test_round_trip(self):
         print_test_name('TEST ROUND TRIP')
@@ -265,7 +265,7 @@ class TestIO(unittest.TestCase):
                 datum = datum.astimezone(tz=timezones.utc)
             if datum == round_trip_datum:
                 correct += 1
-        self.assertEquals(correct, len(SCHEMAS_TO_VALIDATE))
+        self.assertEqual(correct, len(SCHEMAS_TO_VALIDATE))
 
     #
     # BINARY ENCODING OF INT AND LONG
@@ -273,19 +273,19 @@ class TestIO(unittest.TestCase):
 
     def test_binary_int_encoding(self):
         correct = check_binary_encoding('int')
-        self.assertEquals(correct, len(BINARY_ENCODINGS))
+        self.assertEqual(correct, len(BINARY_ENCODINGS))
 
     def test_binary_long_encoding(self):
         correct = check_binary_encoding('long')
-        self.assertEquals(correct, len(BINARY_ENCODINGS))
+        self.assertEqual(correct, len(BINARY_ENCODINGS))
 
     def test_skip_int(self):
         correct = check_skip_number('int')
-        self.assertEquals(correct, len(BINARY_ENCODINGS))
+        self.assertEqual(correct, len(BINARY_ENCODINGS))
 
     def test_skip_long(self):
         correct = check_skip_number('long')
-        self.assertEquals(correct, len(BINARY_ENCODINGS))
+        self.assertEqual(correct, len(BINARY_ENCODINGS))
 
     #
     # SCHEMA RESOLUTION
@@ -308,7 +308,7 @@ class TestIO(unittest.TestCase):
                 print('Datum Read: %s' % datum_read)
                 if datum_read != datum_to_write:
                     incorrect += 1
-        self.assertEquals(incorrect, 0)
+        self.assertEqual(incorrect, 0)
 
     def test_unknown_symbol(self):
         print_test_name('TEST UNKNOWN SYMBOL')
@@ -325,7 +325,7 @@ class TestIO(unittest.TestCase):
         reader = io.BytesIO(writer.getvalue())
         decoder = avro.io.BinaryDecoder(reader)
         datum_reader = avro.io.DatumReader(writers_schema, readers_schema)
-        self.assertRaises(avro.io.SchemaResolutionException, 
datum_reader.read, decoder)
+        self.assertRaises(avro.errors.SchemaResolutionException, 
datum_reader.read, decoder)
 
     def test_default_value(self):
         print_test_name('TEST DEFAULT VALUE')
@@ -345,7 +345,7 @@ class TestIO(unittest.TestCase):
             print('Datum Read: %s' % datum_read)
             if datum_to_read == datum_read:
                 correct += 1
-        self.assertEquals(correct, len(DEFAULT_VALUE_EXAMPLES))
+        self.assertEqual(correct, len(DEFAULT_VALUE_EXAMPLES))
 
     def test_no_default_value(self):
         print_test_name('TEST NO DEFAULT VALUE')
@@ -360,7 +360,7 @@ class TestIO(unittest.TestCase):
         reader = io.BytesIO(writer.getvalue())
         decoder = avro.io.BinaryDecoder(reader)
         datum_reader = avro.io.DatumReader(writers_schema, readers_schema)
-        self.assertRaises(avro.io.SchemaResolutionException, 
datum_reader.read, decoder)
+        self.assertRaises(avro.errors.SchemaResolutionException, 
datum_reader.read, decoder)
 
     def test_projection(self):
         print_test_name('TEST PROJECTION')
@@ -376,7 +376,7 @@ class TestIO(unittest.TestCase):
         writer, encoder, datum_writer = write_datum(datum_to_write, 
writers_schema)
         datum_read = read_datum(writer, writers_schema, readers_schema)
         print('Datum Read: %s' % datum_read)
-        self.assertEquals(datum_to_read, datum_read)
+        self.assertEqual(datum_to_read, datum_read)
 
     def test_field_order(self):
         print_test_name('TEST FIELD ORDER')
@@ -392,7 +392,7 @@ class TestIO(unittest.TestCase):
         writer, encoder, datum_writer = write_datum(datum_to_write, 
writers_schema)
         datum_read = read_datum(writer, writers_schema, readers_schema)
         print('Datum Read: %s' % datum_read)
-        self.assertEquals(datum_to_read, datum_read)
+        self.assertEqual(datum_to_read, datum_read)
 
     def test_type_exception(self):
         print_test_name('TEST TYPE EXCEPTION')
@@ -401,7 +401,7 @@ class TestIO(unittest.TestCase):
        "fields": [{"name": "F", "type": "int"},
                   {"name": "E", "type": "int"}]}""")
         datum_to_write = {'E': 5, 'F': 'Bad'}
-        self.assertRaises(avro.io.AvroTypeException, write_datum, 
datum_to_write, writers_schema)
+        self.assertRaises(avro.errors.AvroTypeException, write_datum, 
datum_to_write, writers_schema)
 
 
 if __name__ == '__main__':
diff --git a/lang/py/avro/test/test_protocol.py 
b/lang/py/avro/test/test_protocol.py
index f2b46e7..0e1b2c1 100644
--- a/lang/py/avro/test/test_protocol.py
+++ b/lang/py/avro/test/test_protocol.py
@@ -24,6 +24,7 @@ from __future__ import absolute_import, division, 
print_function
 import json
 import unittest
 
+import avro.errors
 import avro.protocol
 import avro.schema
 
@@ -304,14 +305,14 @@ class ProtocolParseTestCase(unittest.TestCase):
         """Parsing a valid protocol should not error."""
         try:
             self.test_proto.parse()
-        except avro.protocol.ProtocolParseException:
+        except avro.errors.ProtocolParseException:
             self.fail("Valid protocol failed to parse: 
{!s}".format(self.test_proto))
 
     def parse_invalid(self):
         """Parsing an invalid schema should error."""
         try:
             self.test_proto.parse()
-        except (avro.protocol.ProtocolParseException, 
avro.schema.SchemaParseException):
+        except (avro.errors.ProtocolParseException, 
avro.errors.SchemaParseException):
             pass
         else:
             self.fail("Invalid protocol should not have parsed: 
{!s}".format(self.test_proto))
diff --git a/lang/py/avro/test/test_schema.py b/lang/py/avro/test/test_schema.py
index 71d4c5e..472ef41 100644
--- a/lang/py/avro/test/test_schema.py
+++ b/lang/py/avro/test/test_schema.py
@@ -25,6 +25,7 @@ import json
 import unittest
 import warnings
 
+import avro.errors
 from avro import schema
 
 try:
@@ -236,70 +237,70 @@ TIMESTAMPMICROS_LOGICAL_TYPE = [
 IGNORED_LOGICAL_TYPE = [
     ValidTestSchema(
         {"type": "string", "logicalType": "uuid"},
-        warnings=[schema.IgnoredLogicalType('Unknown uuid, using string.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown uuid, using 
string.')]),
     ValidTestSchema(
         {"type": "string", "logicalType": "unknown-logical-type"},
-        warnings=[schema.IgnoredLogicalType('Unknown unknown-logical-type, 
using string.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown 
unknown-logical-type, using string.')]),
     ValidTestSchema(
         {"type": "bytes", "logicalType": "decimal", "scale": 0},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision None. 
Must be a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
None. Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "bytes", "logicalType": "decimal", "precision": 2.4, "scale": 
0},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision 2.4. 
Must be a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
2.4. Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 
-2},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal scale -2. Must be 
a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal scale -2. 
Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "bytes", "logicalType": "decimal", "precision": -2, "scale": 
2},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision -2. 
Must be a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
-2. Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 
3},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal scale 3. Cannot 
be greater than precision 2.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal scale 3. 
Cannot be greater than precision 2.')]),
     ValidTestSchema(
         {"type": "fixed", "logicalType": "decimal", "name": "TestIgnored", 
"precision": -10, "scale": 2, "size": 5},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision -10. 
Must be a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
-10. Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "fixed", "logicalType": "decimal", "name": "TestIgnored", 
"scale": 2, "size": 5},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision None. 
Must be a positive integer.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
None. Must be a positive integer.')]),
     ValidTestSchema(
         {"type": "fixed", "logicalType": "decimal", "name": "TestIgnored", 
"precision": 2, "scale": 3, "size": 2},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal scale 3. Cannot 
be greater than precision 2.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal scale 3. 
Cannot be greater than precision 2.')]),
     ValidTestSchema(
         {"type": "fixed", "logicalType": "decimal", "name": "TestIgnored", 
"precision": 311, "size": 129},
-        warnings=[schema.IgnoredLogicalType('Invalid decimal precision 311. 
Max is 310.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Invalid decimal precision 
311. Max is 310.')]),
     ValidTestSchema(
         {"type": "float", "logicalType": "decimal", "precision": 2, "scale": 
0},
-        warnings=[schema.IgnoredLogicalType('Logical type decimal requires 
literal type bytes/fixed, not float.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Logical type decimal 
requires literal type bytes/fixed, not float.')]),
     ValidTestSchema(
         {"type": "int", "logicalType": "date1"},
-        warnings=[schema.IgnoredLogicalType('Unknown date1, using int.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown date1, using 
int.')]),
     ValidTestSchema(
         {"type": "long", "logicalType": "date"},
-        warnings=[schema.IgnoredLogicalType('Logical type date requires 
literal type int, not long.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Logical type date requires 
literal type int, not long.')]),
     ValidTestSchema(
         {"type": "int", "logicalType": "time-milis"},
-        warnings=[schema.IgnoredLogicalType('Unknown time-milis, using 
int.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown time-milis, using 
int.')]),
     ValidTestSchema(
         {"type": "long", "logicalType": "time-millis"},
-        warnings=[schema.IgnoredLogicalType('Logical type time-millis requires 
literal type int, not long.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Logical type time-millis 
requires literal type int, not long.')]),
     ValidTestSchema(
         {"type": "long", "logicalType": "time-micro"},
-        warnings=[schema.IgnoredLogicalType('Unknown time-micro, using 
long.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown time-micro, using 
long.')]),
     ValidTestSchema(
         {"type": "int", "logicalType": "time-micros"},
-        warnings=[schema.IgnoredLogicalType('Logical type time-micros requires 
literal type long, not int.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Logical type time-micros 
requires literal type long, not int.')]),
     ValidTestSchema(
         {"type": "long", "logicalType": "timestamp-milis"},
-        warnings=[schema.IgnoredLogicalType('Unknown timestamp-milis, using 
long.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown timestamp-milis, 
using long.')]),
     ValidTestSchema(
         {"type": "int", "logicalType": "timestamp-millis"},
-        warnings=[schema.IgnoredLogicalType('Logical type timestamp-millis 
requires literal type long, not int.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Logical type 
timestamp-millis requires literal type long, not int.')]),
     ValidTestSchema(
         {"type": "long", "logicalType": "timestamp-micro"},
-        warnings=[schema.IgnoredLogicalType('Unknown timestamp-micro, using 
long.')]),
+        warnings=[avro.errors.IgnoredLogicalType('Unknown timestamp-micro, 
using long.')]),
     ValidTestSchema(
         {"type": "int", "logicalType": "timestamp-micros"},
-        warnings=[schema.IgnoredLogicalType('Logical type timestamp-micros 
requires literal type long, not int.')])
+        warnings=[avro.errors.IgnoredLogicalType('Logical type 
timestamp-micros requires literal type long, not int.')])
 ]
 
 EXAMPLES = PRIMITIVE_EXAMPLES
@@ -349,7 +350,7 @@ class TestMisc(unittest.TestCase):
 
     def test_name_not_empty_string(self):
         """A name cannot be the empty string."""
-        self.assertRaises(schema.SchemaParseException, schema.Name, "", None, 
None)
+        self.assertRaises(avro.errors.SchemaParseException, schema.Name, "", 
None, None)
 
     def test_name_space_specified(self):
         """Space combines with a name to become the fullname."""
@@ -390,8 +391,8 @@ class TestMisc(unittest.TestCase):
     def test_invalid_name(self):
         """The name portion of a fullname, record field names, and enum 
symbols must:
            start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]"""
-        self.assertRaises(schema.InvalidName, schema.Name, 'an especially 
spacey cowboy', None, None)
-        self.assertRaises(schema.InvalidName, schema.Name, '99 problems but a 
name aint one', None, None)
+        self.assertRaises(avro.errors.InvalidName, schema.Name, 'an especially 
spacey cowboy', None, None)
+        self.assertRaises(avro.errors.InvalidName, schema.Name, '99 problems 
but a name aint one', None, None)
 
     def test_null_namespace(self):
         """The empty string may be used as a namespace to indicate the null 
namespace."""
@@ -401,7 +402,7 @@ class TestMisc(unittest.TestCase):
 
     def test_exception_is_not_swallowed_on_parse_error(self):
         """A specific exception message should appear on a json parse error."""
-        self.assertRaisesRegexp(schema.SchemaParseException,
+        self.assertRaisesRegexp(avro.errors.SchemaParseException,
                                 r'Error parsing JSON: /not/a/real/file',
                                 schema.parse,
                                 '/not/a/real/file')
@@ -465,7 +466,7 @@ class TestMisc(unittest.TestCase):
 
         try:
             case = schema.parse(test_schema_string, validate_enum_symbols=True)
-        except schema.InvalidName:
+        except avro.errors.InvalidName:
             pass
         else:
             self.fail("When enum symbol validation is enabled, "
@@ -473,7 +474,7 @@ class TestMisc(unittest.TestCase):
 
         try:
             case = schema.parse(test_schema_string, 
validate_enum_symbols=False)
-        except schema.InvalidName:
+        except avro.errors.InvalidName:
             self.fail("When enum symbol validation is disabled, "
                       "an invalid symbol should not raise InvalidName.")
 
@@ -498,7 +499,7 @@ class SchemaParseTestCase(unittest.TestCase):
         with warnings.catch_warnings(record=True) as actual_warnings:
             try:
                 self.test_schema.parse()
-            except (schema.AvroException, schema.SchemaParseException):
+            except (avro.errors.AvroException, 
avro.errors.SchemaParseException):
                 self.fail("Valid schema failed to parse: 
{!s}".format(self.test_schema))
             actual_messages = [str(wmsg.message) for wmsg in actual_warnings]
             if self.test_schema.warnings:
@@ -511,7 +512,7 @@ class SchemaParseTestCase(unittest.TestCase):
         """Parsing an invalid schema should error."""
         try:
             self.test_schema.parse()
-        except (schema.AvroException, schema.SchemaParseException):
+        except (avro.errors.AvroException, avro.errors.SchemaParseException):
             pass
         else:
             self.fail("Invalid schema should not have parsed: 
{!s}".format(self.test_schema))
diff --git a/lang/py/avro/test/txsample_http_client.py 
b/lang/py/avro/test/txsample_http_client.py
index c9d3f7d..ef229ec 100644
--- a/lang/py/avro/test/txsample_http_client.py
+++ b/lang/py/avro/test/txsample_http_client.py
@@ -22,6 +22,7 @@ from __future__ import absolute_import, division, 
print_function
 
 import sys
 
+import avro.errors
 from avro import protocol, txipc
 from twisted.internet import defer, reactor
 from twisted.python.util import println
@@ -57,14 +58,6 @@ SERVER_HOST = 'localhost'
 SERVER_PORT = 9090
 
 
-class UsageError(Exception):
-    def __init__(self, value):
-        self.value = value
-
-    def __str__(self):
-        return repr(self.value)
-
-
 def make_requestor(server_host, server_port, protocol):
     client = txipc.TwistedHTTPTransceiver(SERVER_HOST, SERVER_PORT)
     return txipc.TwistedRequestor(protocol, client)
@@ -72,7 +65,7 @@ def make_requestor(server_host, server_port, protocol):
 
 if __name__ == '__main__':
     if len(sys.argv) not in [4, 5]:
-        raise UsageError("Usage: <to> <from> <body> [<count>]")
+        raise avro.errors.UsageError("Usage: <to> <from> <body> [<count>]")
 
     # client code - attach to the server and send a message
     # fill in the Message record
diff --git a/lang/py/avro/tether/tether_task.py 
b/lang/py/avro/tether/tether_task.py
index 9dc1e8f..465b295 100644
--- a/lang/py/avro/tether/tether_task.py
+++ b/lang/py/avro/tether/tether_task.py
@@ -27,6 +27,7 @@ import sys
 import threading
 import traceback
 
+import avro.errors
 import avro.io
 from avro import ipc, protocol, schema
 
@@ -35,38 +36,27 @@ __all__ = ["TetherTask", "TaskType", "inputProtocol", 
"outputProtocol", "HTTPReq
 # create protocol objects for the input and output protocols
 # The build process should copy InputProtocol.avpr and OutputProtocol.avpr
 # into the same directory as this module
-inputProtocol = None
-outputProtocol = None
 
 TaskType = None
-if (inputProtocol is None):
-    pfile = os.path.split(__file__)[0] + os.sep + "InputProtocol.avpr"
+pfile = os.path.split(__file__)[0] + os.sep + "InputProtocol.avpr"
+with open(pfile, 'r') as hf:
+    prototxt = hf.read()
 
-    if not(os.path.exists(pfile)):
-        raise Exception("Could not locate the InputProtocol: {0} does not 
exist".format(pfile))
+inputProtocol = protocol.parse(prototxt)
 
-    with open(pfile, 'r') as hf:
-        prototxt = hf.read()
+# use a named tuple to represent the tasktype enumeration
+taskschema = inputProtocol.types_dict["TaskType"]
+# Mypy cannot statically type check a dynamically constructed named tuple.
+# Since InputProtocol.avpr is hard-coded here, we can hard-code the symbols.
+_ttype = collections.namedtuple("_tasktype", ("MAP", "REDUCE"))
+TaskType = _ttype(*taskschema.symbols)
 
-    inputProtocol = protocol.parse(prototxt)
+pfile = os.path.split(__file__)[0] + os.sep + "OutputProtocol.avpr"
 
-    # use a named tuple to represent the tasktype enumeration
-    taskschema = inputProtocol.types_dict["TaskType"]
-    # Mypy cannot statically type check a dynamically constructed named tuple.
-    # Since InputProtocol.avpr is hard-coded here, we can hard-code the 
symbols.
-    _ttype = collections.namedtuple("_tasktype", ("MAP", "REDUCE"))
-    TaskType = _ttype(*taskschema.symbols)
+with open(pfile, 'r') as hf:
+    prototxt = hf.read()
 
-if (outputProtocol is None):
-    pfile = os.path.split(__file__)[0] + os.sep + "OutputProtocol.avpr"
-
-    if not(os.path.exists(pfile)):
-        raise Exception("Could not locate the OutputProtocol: {0} does not 
exist".format(pfile))
-
-    with open(pfile, 'r') as hf:
-        prototxt = hf.read()
-
-    outputProtocol = protocol.parse(prototxt)
+outputProtocol = protocol.parse(prototxt)
 
 
 class Collector(object):
@@ -74,9 +64,8 @@ class Collector(object):
     Collector for map and reduce output values
     """
 
-    def __init__(self, scheme=None, outputClient=None):
+    def __init__(self, scheme, outputClient):
         """
-
         Parameters
         ---------------------------------------------
         scheme - The scheme for the datums to output - can be a json string
@@ -84,12 +73,9 @@ class Collector(object):
         outputClient - The output client used to send messages to the parent
         """
 
-        if not(isinstance(scheme, schema.Schema)):
+        if not isinstance(scheme, schema.Schema):
             scheme = schema.parse(scheme)
 
-        if (outputClient is None):
-            raise ValueError("output client can't be none.")
-
         self.scheme = scheme
 
         self.datum_writer = avro.io.DatumWriter(writers_schema=self.scheme)
@@ -179,7 +165,7 @@ class TetherTask(object):
     away but wait for space to free up)
     """
 
-    def __init__(self, inschema=None, midschema=None, outschema=None):
+    def __init__(self, inschema, midschema, outschema):
         """
 
         Parameters
@@ -203,16 +189,6 @@ class TetherTask(object):
         the differences (see 
https://avro.apache.org/docs/current/spec.html#Schema+Resolution))
 
         """
-
-        if (inschema is None):
-            raise ValueError("inschema can't be None")
-
-        if (midschema is None):
-            raise ValueError("midschema can't be None")
-
-        if (outschema is None):
-            raise ValueError("outschema can't be None")
-
         # make sure we can parse the schemas
         # Should we call fail if we can't parse the schemas?
         self.inschema = schema.parse(inschema)
@@ -266,32 +242,20 @@ class TetherTask(object):
         # The port the parent process is listening on is set in the environment
         # variable AVRO_TETHER_OUTPUT_PORT
         # open output client, connecting to parent
-
-        if (clientPort is None):
-            clientPortString = os.getenv("AVRO_TETHER_OUTPUT_PORT")
-            if (clientPortString is None):
-                raise Exception("AVRO_TETHER_OUTPUT_PORT env var is not set")
-
-            clientPort = int(clientPortString)
+        clientPort = int(clientPort or os.getenv("AVRO_TETHER_OUTPUT_PORT", 0))
+        if clientPort == 0:
+            raise avro.errors.UsageError("AVRO_TETHER_OUTPUT_PORT env var is 
not set")
 
         self.log.info("TetherTask.open: Opening connection to parent server on 
port={0}".format(clientPort))
 
-        # We use the HTTP protocol although we hope to shortly have
-        # support for SocketServer,
-        usehttp = True
-
-        if(usehttp):
-            # self.outputClient =  ipc.Requestor(outputProtocol, 
self.clientTransceiver)
-            # since HTTP is stateless, a new transciever
-            # is created and closed for each request. We therefore set 
clientTransciever to None
-            # We still declare clientTransciever because for other (state) 
protocols we will need
-            # it and we want to check when we get the message fail whether the 
transciever
-            # needs to be closed.
-            # self.clientTranciever=None
-            self.outputClient = HTTPRequestor("127.0.0.1", clientPort, 
outputProtocol)
-
-        else:
-            raise NotImplementedError("Only http protocol is currently 
supported")
+        # self.outputClient =  ipc.Requestor(outputProtocol, 
self.clientTransceiver)
+        # since HTTP is stateless, a new transciever
+        # is created and closed for each request. We therefore set 
clientTransciever to None
+        # We still declare clientTransciever because for other (state) 
protocols we will need
+        # it and we want to check when we get the message fail whether the 
transciever
+        # needs to be closed.
+        # self.clientTranciever=None
+        self.outputClient = HTTPRequestor("127.0.0.1", clientPort, 
outputProtocol)
 
         try:
             self.outputClient.request('configure', {"port": inputport})
diff --git a/lang/py/avro/tether/tether_task_runner.py 
b/lang/py/avro/tether/tether_task_runner.py
index 2e00c61..d5d926c 100644
--- a/lang/py/avro/tether/tether_task_runner.py
+++ b/lang/py/avro/tether/tether_task_runner.py
@@ -25,6 +25,7 @@ import threading
 import traceback
 import weakref
 
+import avro.errors
 import avro.tether.tether_task
 import avro.tether.util
 from avro import ipc
@@ -32,7 +33,7 @@ from avro import ipc
 try:
     import BaseHTTPServer as http_server  # type: ignore
 except ImportError:
-    import http.server as http_server  # type: ignore
+    from http import server as http_server  # type: ignore
 
 __all__ = ["TaskRunner"]
 
@@ -89,8 +90,8 @@ class TaskRunnerResponder(ipc.Responder):
 
         except Exception as e:
             self.log.error("Error occured while processing message: 
{0}".format(message.name))
-            emsg = traceback.format_exc()
-            self.task.fail(emsg)
+            e = traceback.format_exc()
+            self.task.fail(e)
 
         return None
 
@@ -153,10 +154,9 @@ class TaskRunner(object):
         task - An instance of tether task
         """
         self.log = logging.getLogger("TaskRunner:")
-        self.task = task
-
         if not isinstance(task, avro.tether.tether_task.TetherTask):
-            raise ValueError("task must be an instance of tether task")
+            raise avro.errors.AvroException("task must be an instance of 
tether task")
+        self.task = task
 
     def start(self, outputport=None, join=True):
         """
@@ -213,11 +213,11 @@ if __name__ == '__main__':
     # logging.basicConfig(level=logging.INFO,filename='/tmp/log',filemode='w')
     logging.basicConfig(level=logging.INFO)
 
-    if (len(sys.argv) <= 1):
-        print("Error: tether_task_runner.__main__: Usage: tether_task_runner 
task_package.task_module.TaskClass")
-        raise ValueError("Usage: tether_task_runner 
task_package.task_module.TaskClass")
+    try:
+        fullcls = sys.argv[1]
+    except IndexError:
+        raise avro.errors.UsageError("Usage: tether_task_runner 
task_package.task_module.TaskClass")
 
-    fullcls = sys.argv[1]
     mod, cname = fullcls.rsplit(".", 1)
 
     logging.info("tether_task_runner.__main__: Task: {0}".format(fullcls))
@@ -227,5 +227,9 @@ if __name__ == '__main__':
     taskcls = getattr(modobj, cname)
     task = taskcls()
 
-    runner = TaskRunner(task=task)
+    try:
+        runner = TaskRunner(task=task)
+    except avro.errors.AvroException as e:
+        raise avro.errors.UsageError(e)
+
     runner.start()
diff --git a/lang/py/scripts/avro b/lang/py/scripts/avro
index 7058d05..6a069ee 100755
--- a/lang/py/scripts/avro
+++ b/lang/py/scripts/avro
@@ -29,6 +29,7 @@ from functools import partial
 from optparse import OptionGroup, OptionParser
 
 import avro
+import avro.errors
 import avro.schema
 from avro.datafile import DataFileReader, DataFileWriter
 from avro.io import DatumReader, DatumWriter
@@ -57,10 +58,6 @@ def _version():
 _AVRO_VERSION = _version()
 
 
-class AvroError(Exception):
-    pass
-
-
 def print_json(row):
     print(json.dumps(row))
 
@@ -112,7 +109,7 @@ def field_selector(fields):
 
 def print_avro(avro, opts):
     if opts.header and (opts.format != "csv"):
-        raise AvroError("--header applies only to CSV format")
+        raise avro.errors.UsageError("--header applies only to CSV format")
 
     # Apply filter first
     if opts.filter:
@@ -145,7 +142,7 @@ def print_schema(avro):
 
 def cat(opts, args):
     if not args:
-        raise AvroError("No files to show")
+        raise avro.errors.UsageError("No files to show")
     for filename in args:
         with DataFileReader(open(filename, 'rb'), DatumReader()) as avro:
             if opts.print_schema:
@@ -221,11 +218,11 @@ def guess_input_type(files):
 
 def write(opts, files):
     if not opts.schema:
-        raise AvroError("No schema specified")
+        raise avro.errors.UsageError("No schema specified")
 
     input_type = opts.input_type or guess_input_type(files)
     if not input_type:
-        raise AvroError("Can't guess input file type (not .json or .csv)")
+        raise avro.errors.UsageError("Can't guess input file type (not .json 
or .csv)")
     iter_records = {"json": iter_json, "csv": iter_csv}[input_type]
 
     try:
@@ -233,7 +230,7 @@ def write(opts, files):
             schema = avro.schema.parse(schema_file.read())
         out = _open(opts.output, "wb")
     except (IOError, OSError) as e:
-        raise AvroError("Can't open file - %s" % e)
+        raise avro.errors.UsageError("Can't open file - %s" % e)
 
     writer = DataFileWriter(getattr(out, 'buffer', out), DatumWriter(), schema)
 
@@ -245,14 +242,11 @@ def write(opts, files):
     writer.close()
 
 
-def main(argv=None):
-    argv = argv or sys.argv
-
+def main(argv):
     parser = OptionParser(description="Display/write for Avro files",
                           version=_AVRO_VERSION,
                           usage="usage: %prog cat|write [options] FILE 
[FILE...]")
     # cat options
-
     cat_options = OptionGroup(parser, "cat options")
     cat_options.add_option("-n", "--count", default=float("Infinity"),
                            help="number of records to print", type=int)
@@ -284,17 +278,16 @@ def main(argv=None):
     if len(args) < 1:
         parser.error("You much specify `cat` or `write`")  # Will exit
 
-    command = args.pop(0)
+    command_name = args.pop(0)
     try:
-        if command == "cat":
-            cat(opts, args)
-        elif command == "write":
-            write(opts, args)
-        else:
-            raise AvroError("Unknown command - %s" % command)
-    except AvroError as e:
-        parser.error("%s" % e)  # Will exit
+        command = {
+            "cat": cat,
+            "write": write,
+        }[command_name]
+    except KeyError:
+        raise avro.errors.UsageError("Unknown command - 
{!s}".format(command_name))
+    command(opts, args)
 
 
 if __name__ == "__main__":
-    main()
+    main(sys.argv)

Reply via email to