Skip to content

Commit

Permalink
AVRO-2321: Avoid hasattr in Python 2 (#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
kojiromike authored and Fokko committed Feb 12, 2019
1 parent e44accc commit 2aa4946
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 51 deletions.
41 changes: 20 additions & 21 deletions lang/py/src/avro/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# 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
#
#
# http://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.
Expand Down Expand Up @@ -62,12 +62,13 @@ def _parse_messages(self, messages, names):
if message_objects.has_key(name):
fail_msg = 'Message name "%s" repeated.' % name
raise ProtocolParseException(fail_msg)
elif not(hasattr(body, 'get') and callable(body.get)):
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)
request = body.get('request')
response = body.get('response')
errors = body.get('errors')
message_objects[name] = Message(name, request, response, errors, names)
return message_objects

Expand All @@ -79,21 +80,20 @@ def __init__(self, name, namespace=None, types=None, messages=None):
elif not isinstance(name, basestring):
fail_msg = 'The name property must be a string.'
raise ProtocolParseException(fail_msg)
elif namespace is not None and not isinstance(namespace, basestring):
elif not (namespace is None or isinstance(namespace, basestring)):
fail_msg = 'The namespace property must be a string.'
raise ProtocolParseException(fail_msg)
elif types is not None and not isinstance(types, list):
elif not (types is None or isinstance(types, list)):
fail_msg = 'The types property must be a list.'
raise ProtocolParseException(fail_msg)
elif (messages is not None and
not(hasattr(messages, 'get') and callable(messages.get))):
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)

self._props = {}
self.set_prop('name', name)
type_names = schema.Names()
if namespace is not None:
if namespace is not None:
self.set_prop('namespace', namespace)
type_names.default_namespace = namespace
if types is not None:
Expand All @@ -105,7 +105,7 @@ def __init__(self, name, namespace=None, types=None, messages=None):
# read-only properties
name = property(lambda self: self.get_prop('name'))
namespace = property(lambda self: self.get_prop('namespace'))
fullname = property(lambda self:
fullname = property(lambda self:
schema.Name(self.name, self.namespace).fullname)
types = property(lambda self: self.get_prop('types'))
types_dict = property(lambda self: dict([(type.name, type)
Expand All @@ -118,13 +118,13 @@ def __init__(self, name, namespace=None, types=None, messages=None):
def get_prop(self, key):
return self.props.get(key)
def set_prop(self, key, value):
self.props[key] = value
self.props[key] = value

def to_json(self):
to_dump = {}
to_dump['protocol'] = self.name
names = schema.Names(default_namespace=self.namespace)
if self.namespace:
if self.namespace:
to_dump['namespace'] = self.namespace
if self.types:
to_dump['types'] = [ t.to_json(names) for t in self.types ]
Expand All @@ -149,7 +149,7 @@ def _parse_request(self, request, names):
fail_msg = 'Request property not a list: %s' % request
raise ProtocolParseException(fail_msg)
return schema.RecordSchema(None, None, request, names, 'request')

def _parse_response(self, response, names):
if isinstance(response, basestring) and names.has_name(response, None):
return names.get_name(response, None)
Expand Down Expand Up @@ -183,7 +183,7 @@ def __init__(self, name, request, response, errors=None, names=None):
def get_prop(self, key):
return self.props.get(key)
def set_prop(self, key, value):
self.props[key] = value
self.props[key] = value

def __str__(self):
return json.dumps(self.to_json())
Expand All @@ -200,17 +200,17 @@ def to_json(self, names=None):

def __eq__(self, that):
return self.name == that.name and self.props == that.props

def make_avpr_object(json_data):
"""Build Avro Protocol from data parsed out of JSON string."""
if hasattr(json_data, 'get') and callable(json_data.get):
try:
name = json_data.get('protocol')
namespace = json_data.get('namespace')
types = json_data.get('types')
messages = json_data.get('messages')
return Protocol(name, namespace, types, messages)
else:
except AttributeError:
raise ProtocolParseException('Not a JSON object: %s' % json_data)
return Protocol(name, namespace, types, messages)

def parse(json_string):
"""Constructs the Protocol from the JSON text."""
Expand All @@ -221,4 +221,3 @@ def parse(json_string):

# construct the Avro Protocol object
return make_avpr_object(json_data)

62 changes: 32 additions & 30 deletions lang/py/src/avro/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
# 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
#
#
# http://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.
Expand Down Expand Up @@ -113,6 +113,7 @@ class SchemaParseException(AvroException):

class Schema(object):
"""Base class for all Schema classes."""
_props = None
def __init__(self, type, other_props=None):
# Ensure valid ctor args
if not isinstance(type, basestring):
Expand All @@ -123,13 +124,14 @@ def __init__(self, type, other_props=None):
raise SchemaParseException(fail_msg)

# add members
if not hasattr(self, '_props'): self._props = {}
if self._props is None:
self._props = {}
self.set_prop('type', type)
self.type = type
self._props.update(other_props or {})

# Read-only properties dict. Printing schemas
# creates JSON properties directly from this dict.
# creates JSON properties directly from this dict.
props = property(lambda self: self._props)

# Read-only property dict. Non-reserved properties
Expand Down Expand Up @@ -158,11 +160,11 @@ def to_json(self, names):

class Name(object):
"""Class to describe Avro name."""

def __init__(self, name_attr, space_attr, default_space):
"""
Formulate full name according to the specification.
@arg name_attr: name value read in schema or None.
@arg space_attr: namespace value read in schema or None.
@ard default_space: the current default space or None.
Expand All @@ -181,19 +183,19 @@ def __init__(self, name_attr, space_attr, default_space):
elif name_attr == "":
fail_msg = 'Space must be non-empty string or None.'
raise SchemaParseException(fail_msg)

if not (isinstance(default_space, basestring) or (default_space is None)):
fail_msg = 'Default space must be non-empty string or None.'
raise SchemaParseException(fail_msg)
elif name_attr == "":
fail_msg = 'Default must be non-empty string or None.'
raise SchemaParseException(fail_msg)
self._full = None;

self._full = None;

if name_attr is None or name_attr == "":
return;

if (name_attr.find('.') < 0):
if (space_attr is not None) and (space_attr != ""):
self._full = "%s.%s" % (space_attr, name_attr)
Expand All @@ -203,20 +205,20 @@ def __init__(self, name_attr, space_attr, default_space):
else:
self._full = name_attr
else:
self._full = name_attr
self._full = name_attr

def __eq__(self, other):
if not isinstance(other, Name):
return False
return (self.fullname == other.fullname)

fullname = property(lambda self: self._full)

def get_space(self):
"""Back out a namespace from full name."""
if self._full is None:
return None

if (self._full.find('.') > 0):
return self._full.rsplit(".", 1)[0]
else:
Expand All @@ -227,17 +229,17 @@ class Names(object):
def __init__(self, default_namespace=None):
self.names = {}
self.default_namespace = default_namespace

def has_name(self, name_attr, space_attr):
test = Name(name_attr, space_attr, self.default_namespace).fullname
return self.names.has_key(test)
def get_name(self, name_attr, space_attr):

def get_name(self, name_attr, space_attr):
test = Name(name_attr, space_attr, self.default_namespace).fullname
if not self.names.has_key(test):
return None
return self.names[test]

def prune_namespace(self, properties):
"""given a properties, return properties with namespace removed if
it matches the own default namespace"""
Expand All @@ -258,14 +260,14 @@ def prune_namespace(self, properties):
def add_name(self, name_attr, space_attr, new_schema):
"""
Add a new schema object to the name set.
@arg name_attr: name value read in schema
@arg space_attr: namespace value read in schema.
@return: the Name that was just added.
"""
to_add = Name(name_attr, space_attr, self.default_namespace)

if to_add.fullname in VALID_TYPES:
fail_msg = '%s is a reserved type name.' % to_add.fullname
raise SchemaParseException(fail_msg)
Expand Down Expand Up @@ -298,12 +300,12 @@ def __init__(self, type, name, namespace=None, names=None, other_props=None):

# Store name and namespace as they were read in origin schema
self.set_prop('name', name)
if namespace is not None:
if namespace is not None:
self.set_prop('namespace', new_name.get_space())

# Store full name as calculated from name, namespace
self._fullname = new_name.fullname

def name_ref(self, names):
if self.namespace == names.default_namespace:
return self.name
Expand Down Expand Up @@ -699,8 +701,8 @@ def make_field_objects(field_data, names):
field_objects = []
field_names = []
for i, field in enumerate(field_data):
if hasattr(field, 'get') and callable(field.get):
type = field.get('type')
if callable(getattr(field, 'get', None)):
type = field.get('type')
name = field.get('name')

# null values can have a default value of None
Expand Down Expand Up @@ -742,7 +744,7 @@ def __init__(self, name, namespace, fields, names=None, schema_type='record',
NamedSchema.__init__(self, schema_type, name, namespace, names,
other_props)

if schema_type == 'record':
if schema_type == 'record':
old_default = names.default_namespace
names.default_namespace = Name(name, namespace,
names.default_namespace).get_space()
Expand Down Expand Up @@ -794,7 +796,7 @@ def get_other_props(all_props,reserved_props):
Retrieve the non-reserved properties from a dictionary of properties
@args reserved_props: The set of reserved properties to exclude
"""
if hasattr(all_props, 'items') and callable(all_props.items):
if callable(getattr(all_props, 'items', None)):
return dict([(k,v) for (k,v) in all_props.items() if k not in
reserved_props ])

Expand All @@ -807,9 +809,9 @@ def make_avsc_object(json_data, names=None):
"""
if names == None:
names = Names()

# JSON object (non-union)
if hasattr(json_data, 'get') and callable(json_data.get):
if callable(getattr(json_data, 'get', None)):
type = json_data.get('type')
other_props = get_other_props(json_data, SCHEMA_RESERVED_PROPS)
logical_type = None
Expand Down

0 comments on commit 2aa4946

Please sign in to comment.