blob: 1852cd973f0afe4d53084cd657c6cc2cceb04385 [file]
#
# 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
#
# 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. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import base64
import _pulsar
from .schema import Schema
try:
from google.protobuf import descriptor_pb2
from google.protobuf.message import Message as ProtobufMessage
HAS_PROTOBUF = True
except ImportError:
HAS_PROTOBUF = False
def _collect_file_descriptors(file_descriptor, visited, file_descriptor_set):
"""Recursively collect all FileDescriptorProto objects into file_descriptor_set."""
if file_descriptor.name in visited:
return
for dep in file_descriptor.dependencies:
_collect_file_descriptors(dep, visited, file_descriptor_set)
visited.add(file_descriptor.name)
proto = descriptor_pb2.FileDescriptorProto()
file_descriptor.CopyToProto(proto)
file_descriptor_set.file.append(proto)
def _build_schema_definition(descriptor):
"""
Build the schema definition dict used by Java's ``ProtobufNativeSchemaData``.
The returned mapping has these keys:
.. code-block:: text
fileDescriptorSet
rootMessageTypeName
rootFileDescriptorName
``fileDescriptorSet`` contains base64-encoded ``FileDescriptorSet`` bytes.
This mirrors ``ProtobufNativeSchemaUtils.serialize()`` in the Java client.
"""
file_descriptor_set = descriptor_pb2.FileDescriptorSet()
_collect_file_descriptors(descriptor.file, set(), file_descriptor_set)
file_descriptor_set_bytes = file_descriptor_set.SerializeToString()
return {
"fileDescriptorSet": base64.b64encode(file_descriptor_set_bytes).decode('utf-8'),
"rootMessageTypeName": descriptor.full_name,
"rootFileDescriptorName": descriptor.file.name,
}
if HAS_PROTOBUF:
class ProtobufNativeSchema(Schema):
"""
Schema for protobuf messages using the native protobuf binary encoding.
The schema definition is stored as a JSON-encoded ProtobufNativeSchemaData
(fileDescriptorSet, rootMessageTypeName, rootFileDescriptorName), which is
compatible with the Java client's ProtobufNativeSchema.
Parameters
----------
record_cls:
A generated protobuf message class (subclass of google.protobuf.message.Message).
Example
-------
.. code-block:: python
import pulsar
from pulsar.schema import ProtobufNativeSchema
from my_proto_pb2 import MyMessage
client = pulsar.Client('pulsar://localhost:6650')
schema = ProtobufNativeSchema(MyMessage)
producer = client.create_producer('my-topic', schema=schema)
consumer = client.subscribe('my-topic', 'my-sub', schema=schema)
message = MyMessage()
message.field = 'value'
producer.send(message)
received = consumer.receive(timeout_millis=5000)
typed_value = received.value()
consumer.acknowledge(received)
assert isinstance(typed_value, MyMessage)
assert typed_value.field == 'value'
consumer.close()
producer.close()
client.close()
"""
def __init__(self, record_cls):
if not (isinstance(record_cls, type) and issubclass(record_cls, ProtobufMessage)):
raise TypeError(
f'record_cls must be a protobuf Message subclass, got {record_cls!r}'
)
schema_definition = _build_schema_definition(record_cls.DESCRIPTOR)
super(ProtobufNativeSchema, self).__init__(
record_cls, _pulsar.SchemaType.PROTOBUF_NATIVE, schema_definition, 'PROTOBUF_NATIVE'
)
def encode(self, obj):
self._validate_object_type(obj)
return obj.SerializeToString()
def decode(self, data):
return self._record_cls.FromString(data)
def __str__(self):
return f'ProtobufNativeSchema({self._record_cls.__name__})'
else:
class ProtobufNativeSchema(Schema):
def __init__(self, _record_cls=None):
raise Exception(
"protobuf library support was not found. "
"Install it with: pip install protobuf"
)
def encode(self, obj):
pass
def decode(self, data):
pass