-
Notifications
You must be signed in to change notification settings - Fork 6
Basic Usage
dogtopus edited this page Nov 16, 2023
·
9 revisions
MiniPB supports 3 different flavors of schema declaration methods: Message classes (object serialization), Key-value schema (dict serialization), format string (tuple serialization). The expected Input/Output formats and Pros/Cons listed below each approach
import minipb
### Encode/Decode a Message with schema defined via Fields
@minipb.process_message_fields
class HelloWorldMessage(minipb.Message):
msg = minipb.Field(1, minipb.TYPE_STRING)
# Creating a Message instance
# Method 1: init with kwargs work!
msg_obj = HelloWorldMessage(msg='Hello world!')
# Method 2: from_dict, iterates over all Field's declared in order on the class
msg_obj = HelloWorldMessage.from_dict({'msg': 'Hello world!'})
# Encode a message
encoded_msg = msg_obj.encode()
# encoded_message == b'\n\x0cHello world!'
# Decode a message
decoded_msg_obj = HelloWorldMessage.decode(encoded_msg)
# decoded_msg == HelloWorldMessage(msg='Hello world!')
decoded_dict = decoded_msg_obj.to_dict()
# decoded_dict == {'msg': 'Hello world!'}
Usage
- Define a schema by subclassing
Message
and useField
s and the@process_message_fields
decorator -
instance.encode()
outputs a bytes object -
MessageClass.decode()
takes a bytes object and returns an instance of theMessageClass
Pros
- Most Pythonic way to work with dataclass like objects
- Best IDE support (for completion, typing, etc.)
- Explicitly allows for the specification of non-consecutive field-numbers (useful if you're mirroring an existing .proto definition)
Cons
- For microcontrollers, introduces Object management overhead. May not be suitable for memory constrained applications
import minipb
### Encode/Decode a message with the Wire object and Format String
hello_world_msg = minipb.Wire('U')
# Encode a message
encoded_msg = hello_world_msg.encode('Hello world!')
# encoded_message == b'\n\x0cHello world!'
# Decode a message
decoded_msg = hello_world_msg.decode(encoded_msg)
# decoded_msg == ('Hello world!',)
Usage
- Define a schema by passing a format-string to the
Wire
object -
wire_instance.encode()
takes a tuple of objects and outputs a bytes object -
Wire.decode()
takes a bytes object and outputs a tuple of objects
Pros
- Least memory overhead of all 3 approaches, no dicts required for input/output
Cons
- Format-string may be harder to work with compared to Message class approach
- Does not directly support the specification of non-consecutive field-numbers (need to use field seek to emulate such behavior)
import minipb
### Encode/Decode a message with the Wire object and Key-Value Schema
# Create the Wire object with schema
hello_world_msg = minipb.Wire([
('msg', 'U') # 'U' means UTF-8 string.
])
# Encode a message
encoded_msg = hello_world_msg.encode({
'msg': 'Hello world!'
})
# encoded_message == b'\n\x0cHello world!'
# Decode a message
decoded_msg = hello_world_msg.decode(encoded_msg)
# decoded_msg == {'msg': 'Hello world!'}
Usage
- Define a schema by passing a key-value-format-list to the
Wire
object -
wire_instance.encode()
takes a single dictionary with keys corresponding to the schema and outputs a bytes object -
Wire.decode()
takes a bytes objective and outputs a dictionary
Pros
- Less Object management overhead vs Message class approach
Cons
- Key-value-format-list may be harder to work with compared to Message class approach
- Does not directly support non-consecutive field-numbers (need to use field seek to emulate such behavior)
To serialize some objects, use:
encoded = schema.encode('hello', 1) # if using format string
encoded = schema.encode({'some_string': 'hello', 'some_int': 1}) # if using key-value format list
# Alternatively, use this to do one-off serialization without explicitly create a `Wire` object.
encoded = minipb.encode('UV', 'hello', 1)
# Same style also works for key-value format list, but looks less pretty.
Similarly, to deserialize some objects, use:
decoded = schema.decode(encoded)
# Returns ('hello', 1) if using format string, or {'some_string': 'hello', 'some_int': 1} if using key-value format list
# Alternatively, use this to do one-off deserialization without explicitly create a `Wire` object.
decoded = minipb.decode('UV', encoded)
MiniPB offers the RawWire
class that encodes and decodes data without a schema. For decoding it simply splits all the fields in the serialized bytes into a list of dictionaries that contains all the information included in the original serialized data. Similarly the encoding assembles the output from the decoder back to bytes.
TODO: add examples