Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add wrapper types for all integer/float (de)serialization #322

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 90 additions & 3 deletions src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,19 @@ enum SupportedType {
ByteArray,
Str,
Int,
Int8,
Int16,
Int32,
Int64,
Int128,
UInt8,
UInt16,
UInt32,
UInt64,
UInt128,
Float,
Float32,
Float64,
Bool,
List,
Tuple,
Expand All @@ -48,16 +60,33 @@ enum SupportedType {

impl SupportedType {
fn init_dict(py: Python) -> Py<PyDict> {
let dict = PyDict::new_bound(py);
fn add_type<T: PyTypeInfo>(py: Python, dict: &Bound<PyDict>, tp: SupportedType) {
dict.set_item(T::type_object_bound(py), tp as u8).unwrap()
}
let dict = PyDict::new_bound(py);
let zenoh = py.import_bound("zenoh").unwrap();
let add_wrapper_type = |name, tp| {
let wrapper = zenoh.getattr(name).unwrap();
dict.set_item(wrapper, tp as u8).unwrap();
};
add_type::<ZBytes>(py, &dict, SupportedType::ZBytes);
add_type::<PyBytes>(py, &dict, SupportedType::Bytes);
add_type::<PyByteArray>(py, &dict, SupportedType::ByteArray);
add_type::<PyString>(py, &dict, SupportedType::Str);
add_type::<PyInt>(py, &dict, SupportedType::Int);
add_wrapper_type("Int8", SupportedType::Int8);
add_wrapper_type("Int16", SupportedType::Int16);
add_wrapper_type("Int32", SupportedType::Int32);
add_wrapper_type("Int64", SupportedType::Int64);
add_wrapper_type("Int128", SupportedType::Int128);
add_wrapper_type("UInt8", SupportedType::UInt8);
add_wrapper_type("UInt16", SupportedType::UInt16);
add_wrapper_type("UInt32", SupportedType::UInt32);
add_wrapper_type("UInt64", SupportedType::UInt64);
add_wrapper_type("UInt128", SupportedType::UInt128);
add_type::<PyFloat>(py, &dict, SupportedType::Float);
add_wrapper_type("Float32", SupportedType::Float32);
add_wrapper_type("Float64", SupportedType::Float64);
add_type::<PyBool>(py, &dict, SupportedType::Bool);
add_type::<PyList>(py, &dict, SupportedType::List);
add_type::<PyTuple>(py, &dict, SupportedType::Tuple);
Expand All @@ -74,7 +103,19 @@ impl SupportedType {
n if n == Self::ByteArray as u8 => Some(Self::ByteArray),
n if n == Self::Str as u8 => Some(Self::Str),
n if n == Self::Int as u8 => Some(Self::Int),
n if n == Self::Int8 as u8 => Some(Self::Int8),
n if n == Self::Int16 as u8 => Some(Self::Int16),
n if n == Self::Int32 as u8 => Some(Self::Int32),
n if n == Self::Int64 as u8 => Some(Self::Int64),
n if n == Self::Int128 as u8 => Some(Self::Int128),
n if n == Self::UInt8 as u8 => Some(Self::UInt8),
n if n == Self::UInt16 as u8 => Some(Self::UInt16),
n if n == Self::UInt32 as u8 => Some(Self::UInt32),
n if n == Self::UInt64 as u8 => Some(Self::UInt64),
n if n == Self::UInt128 as u8 => Some(Self::UInt128),
n if n == Self::Float as u8 => Some(Self::Float),
n if n == Self::Float32 as u8 => Some(Self::Float32),
n if n == Self::Float64 as u8 => Some(Self::Float64),
n if n == Self::Bool as u8 => Some(Self::Bool),
n if n == Self::List as u8 => Some(Self::List),
n if n == Self::Tuple as u8 => Some(Self::Tuple),
Expand Down Expand Up @@ -214,8 +255,18 @@ impl ZBytes {
<Vec<u8>>::extract_bound(obj)?.into()
}
SupportedType::Str => String::extract_bound(obj)?.into(),
SupportedType::Int => i64::extract_bound(obj)?.into(),
SupportedType::Float => f64::extract_bound(obj)?.into(),
SupportedType::Int | SupportedType::Int64 => i64::extract_bound(obj)?.into(),
SupportedType::Int8 => i8::extract_bound(obj)?.into(),
SupportedType::Int16 => i16::extract_bound(obj)?.into(),
SupportedType::Int32 => i32::extract_bound(obj)?.into(),
SupportedType::Int128 => i128::extract_bound(obj)?.into(),
SupportedType::UInt8 => u8::extract_bound(obj)?.into(),
SupportedType::UInt16 => u16::extract_bound(obj)?.into(),
SupportedType::UInt32 => u32::extract_bound(obj)?.into(),
SupportedType::UInt64 => u64::extract_bound(obj)?.into(),
SupportedType::UInt128 => u128::extract_bound(obj)?.into(),
SupportedType::Float | SupportedType::Float64 => f64::extract_bound(obj)?.into(),
SupportedType::Float32 => (f64::extract_bound(obj)? as f32).into(),
SupportedType::Bool => bool::extract_bound(obj)?.into(),
SupportedType::List => obj
.downcast::<PyList>()?
Expand Down Expand Up @@ -271,7 +322,43 @@ impl ZBytes {
.into_py(py),
SupportedType::Str => this.0.deserialize::<Cow<str>>().into_pyres()?.into_py(py),
SupportedType::Int => this.0.deserialize::<i64>().into_pyres()?.into_py(py),
SupportedType::Int8 => import!(py, zenoh.Int8)
.call1((this.0.deserialize::<i8>().into_pyres()?,))?
.into_py(py),
SupportedType::Int16 => import!(py, zenoh.Int16)
.call1((this.0.deserialize::<i16>().into_pyres()?,))?
.into_py(py),
SupportedType::Int32 => import!(py, zenoh.Int32)
.call1((this.0.deserialize::<i32>().into_pyres()?,))?
.into_py(py),
SupportedType::Int64 => import!(py, zenoh.Int64)
.call1((this.0.deserialize::<i64>().into_pyres()?,))?
.into_py(py),
SupportedType::Int128 => import!(py, zenoh.Int128)
.call1((this.0.deserialize::<i128>().into_pyres()?,))?
.into_py(py),
SupportedType::UInt8 => import!(py, zenoh.UInt8)
.call1((this.0.deserialize::<u8>().into_pyres()?,))?
.into_py(py),
SupportedType::UInt16 => import!(py, zenoh.UInt16)
.call1((this.0.deserialize::<u16>().into_pyres()?,))?
.into_py(py),
SupportedType::UInt32 => import!(py, zenoh.UInt32)
.call1((this.0.deserialize::<u32>().into_pyres()?,))?
.into_py(py),
SupportedType::UInt64 => import!(py, zenoh.UInt64)
.call1((this.0.deserialize::<u64>().into_pyres()?,))?
.into_py(py),
SupportedType::UInt128 => import!(py, zenoh.UInt128)
.call1((this.0.deserialize::<u128>().into_pyres()?,))?
.into_py(py),
SupportedType::Float => this.0.deserialize::<f64>().into_pyres()?.into_py(py),
SupportedType::Float32 => import!(py, zenoh.Float32)
.call1((this.0.deserialize::<f32>().into_pyres()?,))?
.into_py(py),
SupportedType::Float64 => import!(py, zenoh.Float64)
.call1((this.0.deserialize::<f64>().into_pyres()?,))?
.into_py(py),
SupportedType::Bool => this.0.deserialize::<bool>().into_pyres()?.into_py(py),
SupportedType::List => PyList::new_bound(py, to_vec()).into_py(py),
SupportedType::Tuple => PyTuple::new_bound(py, to_vec()).into_py(py),
Expand Down
25 changes: 23 additions & 2 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,36 @@

import pytest

from zenoh import ZBytes, deserializer, serializer
from zenoh import (
Float32,
Float64,
Int8,
Int16,
Int32,
Int64,
Int128,
UInt8,
UInt16,
UInt32,
UInt64,
UInt128,
ZBytes,
deserializer,
serializer,
)

default_serializer_tests = [
(ZBytes, ZBytes.serialize(b"foo")),
(bytes, b"foo"),
(bytearray, bytearray(b"foo")),
(str, "foo"),
(int, -42),
*((tp, tp(-42)) for tp in (Int8, Int16, Int32, Int64, Int128)),
(int, 42),
*((tp, tp(42)) for tp in (UInt8, UInt16, UInt32, UInt64, UInt128)),
(float, 0.5),
(Float64, Float64(0.5)),
(Float32, Float32(0.5)),
(bool, True),
(list, [ZBytes.serialize(0), ZBytes.serialize(1)]),
(tuple, (ZBytes.serialize(0), ZBytes.serialize(1))),
Expand All @@ -41,7 +62,7 @@
(dict[str, str], {"foo": "bar"}),
(set[int], {0, 1, 2}),
(frozenset[int], frozenset([0, 1, 2])),
(list[tuple[int, int]], [(0, 1), (2, 3)]),
(list[tuple[float, Float32]], [(0.0, Float32(0.5)), (1.5, Float32(2))]),
]


Expand Down
86 changes: 86 additions & 0 deletions zenoh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,89 @@
# ZettaScale Zenoh Team, <[email protected]>
#
from .zenoh import *

_INT8_MIN = -(1 << 7)
_INT16_MIN = -(1 << 15)
_INT32_MIN = -(1 << 31)
_INT64_MIN = -(1 << 63)
_INT128_MIN = -(1 << 127)

_INT8_MAX = 1 << 7
_INT16_MAX = 1 << 15
_INT32_MAX = 1 << 31
_INT64_MAX = 1 << 63
_INT128_MAX = 1 << 127

_UINT8_MAX = 1 << 8
Mallets marked this conversation as resolved.
Show resolved Hide resolved
_UINT16_MAX = 1 << 16
_UINT32_MAX = 1 << 32
_UINT64_MAX = 1 << 64
_UINT128_MAX = 1 << 128


class Int8(int):
def __new__(cls, i: int):
assert _INT8_MIN <= i < _INT8_MAX, f"{i} too big for Int8"
return int.__new__(cls, i)


class Int16(int):
def __new__(cls, i: int):
assert _INT16_MIN <= i < _INT16_MAX, f"{i} too big for Int16"
return int.__new__(cls, i)


class Int32(int):
def __new__(cls, i: int):
assert _INT32_MIN <= i < _INT32_MAX, f"{i} too big for Int32"
return int.__new__(cls, i)


class Int64(int):
def __new__(cls, i: int):
assert _INT64_MIN <= i < _INT64_MAX, f"{i} too big for Int64"
return int.__new__(cls, i)


class Int128(int):
def __new__(cls, i: int):
assert _INT128_MIN <= i < _INT128_MAX, f"{i} too big for Int128"
return int.__new__(cls, i)


class UInt8(int):
def __new__(cls, i: int):
assert 0 <= i < _UINT8_MAX, f"{i} too big for UInt8"
return int.__new__(cls, i)


class UInt16(int):
def __new__(cls, i: int):
assert 0 <= i < _UINT16_MAX, f"{i} too big for UInt16"
return int.__new__(cls, i)


class UInt32(int):
def __new__(cls, i: int):
assert 0 <= i < _UINT32_MAX, f"{i} too big for UInt32"
return int.__new__(cls, i)


class UInt64(int):
def __new__(cls, i: int):
assert 0 <= i < _UINT64_MAX, f"{i} too big for UInt64"
return int.__new__(cls, i)


class UInt128(int):
def __new__(cls, i: int):
assert 0 <= i < _UINT128_MAX, f"{i} too big for UInt128"
return int.__new__(cls, i)


class Float32(float):
pass


class Float64(float):
pass
50 changes: 49 additions & 1 deletion zenoh/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ _RustHandler = (
_PythonCallback = Callable[[_T], Any]
_PythonHandler = tuple[_PythonCallback[_T], _H]

class Int8(int):
"""int subclass enabling to (de)serialize 8bit signed integer."""

class Int16(int):
"""int subclass enabling to (de)serialize 16bit signed integer."""

class Int32(int):
"""int subclass enabling to (de)serialize 32bit signed integer."""

class Int64(int):
"""int subclass enabling to (de)serialize 64bit signed integer."""

class Int128(int):
"""int subclass enabling to (de)serialize 128bit signed integer."""

class UInt8(int):
"""int subclass enabling to (de)serialize 8bit unsigned integer."""

class UInt16(int):
"""int subclass enabling to (de)serialize 16bit unsigned integer."""

class UInt32(int):
"""int subclass enabling to (de)serialize 32bit unsigned integer."""

class UInt64(int):
"""int subclass enabling to (de)serialize 64bit unsigned integer."""

class UInt128(int):
"""int subclass enabling to (de)serialize 128bit unsigned integer."""

class Float32(float):
"""float subclass enabling to (de)serialize 32bit floating point numbers."""

class Float64(float):
"""float subclass enabling to (de)serialize 64bit floating point numbers."""

@final
class ZError(Exception): ...

Expand Down Expand Up @@ -951,7 +987,19 @@ _IntoWhatAmIMatcher = WhatAmIMatcher | str

@final
class ZBytes:
"""ZBytes contains the serialized bytes of user data."""
"""ZBytes contains the serialized bytes of user data.

It provides convenient methods to the user for serialization/deserialization.

**NOTE** Zenoh semantic and protocol take care of sending and receiving bytes
without restricting the actual data types. Default (de)serializers are provided for
convenience to the users to deal with primitives data types via a simple
out-of-the-box encoding. They are NOT by any means the only (de)serializers
users can use nor a limitation to the types supported by Zenoh. Users are free and
encouraged to use any data format of their choice like JSON, protobuf,
flatbuffers, etc. Deserializers/serializers can be registered for arbitrary types
with `zenoh.serializer`/`zenoh.deserializer` decorators, and default ones can be
overridden."""

def __new__(cls, bytes: bytes | bytearray = None) -> Self: ...
@classmethod
Expand Down
Loading