def __init__( self, name, namespace=None, types=tuple(), messages=tuple(), ): """Initializes a new protocol object. Args: name: Protocol name (absolute or relative). namespace: Optional explicit namespace (if name is relative). types: Collection of types in the protocol. messages: Collection of messages in the protocol. """ self._avro_name = schema.Name(name=name, namespace=namespace) self._fullname = self._avro_name.fullname self._name = self._avro_name.simple_name self._namespace = self._avro_name.namespace self._props = {} self._props['name'] = self._name if self._namespace: self._props['namespace'] = self._namespace self._names = schema.Names(default_namespace=self._namespace) self._types = tuple(types) # Map: type full name -> type schema self._type_map = MappingProxyType( {type.fullname: type for type in self._types}) # This assertion cannot fail unless we don't track named schemas properly: assert (len(self._types) == len( self._type_map)), ('Type list %r does not match type map: %r' % (self._types, self._type_map)) # TODO: set props['types'] self._messages = tuple(messages) # Map: message name -> Message # Note that message names are simple names unique within the protocol. self._message_map = MappingProxyType( {message.name: message for message in self._messages}) if len(self._messages) != len(self._message_map): raise ProtocolParseException( 'Invalid protocol %s with duplicate message name: %r' % (self._avro_name, self._messages)) # TODO: set props['messages'] self._md5 = hashlib.md5(str(self).encode('utf-8')).digest()
def test_fullname(self): """The fullname is determined in one of the following ways: * A name and namespace are both specified. For example, one might use "name": "X", "namespace": "org.foo" to indicate the fullname "org.foo.X". * A fullname is specified. If the name specified contains a dot, then it is assumed to be a fullname, and any namespace also specified is ignored. For example, use "name": "org.foo.X" to indicate the fullname "org.foo.X". * A name only is specified, i.e., a name that contains no dots. In this case the namespace is taken from the most tightly encosing schema or protocol. For example, if "name": "X" is specified, and this occurs within a field of the record definition of "org.foo.Y", then the fullname is "org.foo.X". References to previously defined names are as in the latter two cases above: if they contain a dot they are a fullname, if they do not contain a dot, the namespace is the namespace of the enclosing definition. Primitive type names have no namespace and their names may not be defined in any namespace. A schema may only contain multiple definitions of a fullname if the definitions are equivalent. """ # relative name and namespace specified self.assertEqual(schema.Name('a', 'o.a.h').fullname, 'o.a.h.a') # absolute name and namespace specified self.assertEqual(schema.Name('.a', 'o.a.h').fullname, '.a') # absolute name and namespace specified fullname = schema.Name('a.b.c.d', 'o.a.h').fullname self.assertEqual(fullname, 'a.b.c.d')
def ProtocolFromJSONData(json_data): """Builds an Avro Protocol from its JSON descriptor. Args: json_data: JSON data representing the descriptor of the Avro protocol. Returns: The Avro Protocol parsed from the JSON descriptor. Raises: ProtocolParseException: if the descriptor is invalid. """ if type(json_data) != dict: print(type(json_data)) raise ProtocolParseException( 'Invalid JSON descriptor for an Avro protocol: %r' % json_data) name = json_data.get('protocol') if name is None: raise ProtocolParseException( 'Invalid protocol descriptor with no "name": %r' % json_data) # Namespace is optional namespace = json_data.get('namespace') avro_name = schema.Name(name=name, namespace=namespace) names = schema.Names(default_namespace=avro_name.namespace) type_desc_list = json_data.get('types', tuple()) types = tuple( map(lambda desc: Protocol._ParseTypeDesc(desc, names=names), type_desc_list)) message_desc_map = json_data.get('messages', dict()) messages = tuple( Protocol._ParseMessageDescMap(message_desc_map, names=names)) return Protocol( name=name, namespace=namespace, types=types, messages=messages, )
class Protocol(object): """An application protocol.""" def _parse_types(self, types, type_names): type_objects = [] for type in types: type_object = 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) type_objects.append(type_object) return type_objects def _parse_messages(self, messages, names): message_objects = {} for name, body in messages.items(): if name in message_objects: fail_msg = 'Message name "%s" repeated.' % name raise ProtocolParseException(fail_msg) elif not (hasattr(body, 'get') and callable(body.get)): 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 def __init__(self, name, namespace=None, types=None, messages=None): # Ensure valid ctor args if not name: fail_msg = 'Protocols must have a non-empty name.' raise ProtocolParseException(fail_msg) elif not isinstance(name, six.string_types): fail_msg = 'The name property must be a string.' raise ProtocolParseException(fail_msg) elif namespace is not None and not isinstance(namespace, six.string_types): fail_msg = 'The namespace property must be a string.' raise ProtocolParseException(fail_msg) elif types is not None and not 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))): 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: self.set_prop('namespace', namespace) type_names.default_namespace = namespace if types is not None: self.set_prop('types', self._parse_types(types, type_names)) if messages is not None: self.set_prop('messages', self._parse_messages(messages, type_names)) self._md5 = md5(str(self).encode('US-ASCII')).digest() # read-only properties name = property(lambda self: self.get_prop('name')) namespace = property(lambda self: self.get_prop('namespace')) 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) for type in self.types])) messages = property(lambda self: self.get_prop('messages')) md5 = property(lambda self: self._md5) props = property(lambda self: self._props) # utility functions to manipulate properties dict def get_prop(self, key): return self.props.get(key) def set_prop(self, 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: to_dump['namespace'] = self.namespace if self.types: to_dump['types'] = [t.to_json(names) for t in self.types] if self.messages: messages_dict = {} for name, body in self.messages.items(): messages_dict[name] = body.to_json(names) to_dump['messages'] = messages_dict return to_dump def __str__(self): return json.dumps(self.to_json(), sort_keys=True) def __eq__(self, that): to_cmp = json.loads(str(self)) return to_cmp == json.loads(str(that))
def test_null_namespace(self): """The empty string may be used as a namespace to indicate the null namespace.""" name = schema.Name('name', "", None) self.assertEqual(name.fullname, "name") self.assertIsNone(name.space)
def test_equal_names(self): """Equality of names is defined on the fullname and is case-sensitive.""" self.assertEqual(schema.Name('a.b.c.d', None, None), schema.Name('d', 'a.b.c', None)) self.assertNotEqual(schema.Name('C.d', None, None), schema.Name('c.d', None, None))
def test_name_space_default_specified(self): """When name and space are specified, default space should be ignored.""" fullname = schema.Name('a', 'o.a.a', 'o.a.h').fullname self.assertEqual(fullname, 'o.a.a.a')
def test_fullname_space_default_specified(self): """When a name contains dots, namespace and default space should be ignored.""" fullname = schema.Name('a.b.c.d', 'o.a.a', 'o.a.h').fullname self.assertEqual(fullname, 'a.b.c.d')
def test_name_default_specified(self): """Default space becomes the namespace when the namespace is None.""" fullname = schema.Name('a', None, 'b.c.d').fullname self.assertEqual(fullname, 'b.c.d.a')
def test_name_space_specified(self): """Space combines with a name to become the fullname.""" # name and namespace specified fullname = schema.Name('a', 'o.a.h', None).fullname self.assertEqual(fullname, 'o.a.h.a')
def test_name_is_none(self): """When a name is None its namespace is None.""" self.assertIsNone(schema.Name(None, None, None).fullname) self.assertIsNone(schema.Name(None, None, None).space)