Skip to content
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
18 changes: 9 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ members = [

[workspace.package]
authors = ["Sift Software Engineers <engineering@siftstack.com>"]
version = "0.12.0"
version = "0.13.0"
edition = "2024"
categories = ["aerospace", "science::robotics"]
homepage = "https://github.com/sift-stack/sift"
Expand Down Expand Up @@ -87,15 +87,15 @@ tracing-test = { version = "0.2", features = ["no-env-filter"] }
uuid = { version = "1.22", features = ["v4"] }
zip = "8.2"

sift_connect = { version = "0.12.0", path = "rust/crates/sift_connect" }
sift_rs = { version = "0.12.0", path = "rust/crates/sift_rs" }
sift_error = { version = "0.12.0", path = "rust/crates/sift_error" }
sift_stream = { version = "0.12.0", path = "rust/crates/sift_stream" }
sift_pbfs = { version = "0.12.0", path = "rust/crates/sift_pbfs" }
sift_mcp = { version = "0.12.0", path = "rust/crates/sift_mcp" }
sift_test_util = { version = "0.12.0", path = "rust/crates/sift_test_util" }
sift_connect = { version = "0.13.0", path = "rust/crates/sift_connect" }
sift_rs = { version = "0.13.0", path = "rust/crates/sift_rs" }
sift_error = { version = "0.13.0", path = "rust/crates/sift_error" }
sift_stream = { version = "0.13.0", path = "rust/crates/sift_stream" }
sift_pbfs = { version = "0.13.0", path = "rust/crates/sift_pbfs" }
sift_mcp = { version = "0.13.0", path = "rust/crates/sift_mcp" }
sift_test_util = { version = "0.13.0", path = "rust/crates/sift_test_util" }

sift_stream_bindings = { version = "0.5.0", path = "rust/crates/sift_stream_bindings" }
sift_stream_bindings = { version = "0.5.1", path = "rust/crates/sift_stream_bindings" }

# The profile that 'dist' will build with
[profile.dist]
Expand Down
19 changes: 19 additions & 0 deletions rust/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](http://semver.org/).

## [v0.13.0] - September 4, 2026
### What's New

#### Bytes channel values

`Value` has a new `Bytes(Vec<u8>)` variant that ingests as `CHANNEL_DATA_TYPE_BYTES`. A plain
`Vec<u8>` or `&[u8]` still converts to `Value::BitField`; wrap it in the new `ChannelBytes`
newtype to send raw bytes:

```rust
use sift_stream::{ChannelBytes, ChannelValue};

let payload = ChannelValue::new("payload", ChannelBytes(vec![0xde, 0xad]));
```

`sift_stream_bindings` exposes this as `ValuePy.Bytes`, `ValuePy.is_bytes`, `ValuePy.as_bytes`,
and `IngestWithConfigDataChannelValuePy.bytes`. This fixes bytes channel ingestion in the Python
library, which previously raised `ValueError: Invalid data type: bytes`.

## [v0.12.0]
### What's New

Expand Down
2 changes: 1 addition & 1 deletion rust/crates/sift_stream/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ pub use stream::{
FileBackupBuilder, IngestionConfigForm, LiveOnlyBuilder, LiveWithBackupsBuilder, RunForm,
SiftStreamBuilder, StreamConfigBuilder,
},
channel::{ChannelEnum, ChannelValue, Value},
channel::{ChannelBytes, ChannelEnum, ChannelValue, Value},
flow::{ChannelIndex, FlowBuilder, FlowDescriptor, FlowDescriptorBuilder},
mode::{
file_backup::FileBackup,
Expand Down
34 changes: 34 additions & 0 deletions rust/crates/sift_stream/src/stream/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ pub struct ChannelValue {
#[derive(Debug, PartialEq)]
pub struct ChannelEnum(pub u32);

/// Wrapper to distinguish raw bytes from bitfield values, since both are
/// backed by `Vec<u8>`. A plain `Vec<u8>` or `&[u8]` converts to
/// [`Value::BitField`]; wrap it in `ChannelBytes` to produce [`Value::Bytes`].
///
/// # Example
///
/// ```
/// use sift_stream::{ChannelValue, ChannelBytes};
///
/// let bytes_value = ChannelValue::new("payload", ChannelBytes(vec![0xde, 0xad]));
/// ```
#[derive(Debug, PartialEq)]
pub struct ChannelBytes(pub Vec<u8>);

/// Represents a typed value emitted by a channel.
///
/// This enum covers all supported data types for telemetry channels. Values can
Expand Down Expand Up @@ -65,6 +79,7 @@ pub enum Value {
Uint64(u64),
Enum(u32),
BitField(Vec<u8>),
Bytes(Vec<u8>),
}

impl Value {
Expand All @@ -81,6 +96,7 @@ impl Value {
Value::Uint64(_) => ChannelDataType::Uint64,
Value::Enum(_) => ChannelDataType::Enum,
Value::BitField(_) => ChannelDataType::BitField,
Value::Bytes(_) => ChannelDataType::Bytes,
}
}

Expand All @@ -97,6 +113,7 @@ impl Value {
Value::Uint64(val) => Type::Uint64(*val),
Value::Enum(val) => Type::Enum(*val),
Value::BitField(val) => Type::BitField(val.clone()),
Value::Bytes(val) => Type::Bytes(val.clone()),
}
}
}
Expand Down Expand Up @@ -192,6 +209,12 @@ impl From<ChannelEnum> for Value {
}
}

impl From<ChannelBytes> for Value {
fn from(value: ChannelBytes) -> Self {
Value::Bytes(value.0)
}
}

impl From<u32> for Value {
fn from(value: u32) -> Self {
Value::Uint32(value)
Expand Down Expand Up @@ -335,4 +358,15 @@ fn test_channel_value_conversion() {
},
bitfield_val
);

let bytes_val = ChannelValue::new("channel", ChannelBytes(vec![0xde, 0xad]));
assert_eq!(
ChannelValue {
name: String::from("channel"),
value: Value::Bytes(vec![0xde, 0xad])
},
bytes_val
);
assert_eq!(bytes_val.value.pb_data_type(), ChannelDataType::Bytes);
assert_eq!(bytes_val.value.pb_value(), Type::Bytes(vec![0xde, 0xad]));
}
2 changes: 1 addition & 1 deletion rust/crates/sift_stream_bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sift-stream-bindings"
version = "0.5.0"
version = "0.5.1"
# Released to PyPI as a wheel, never to crates.io.
publish = false
edition = { workspace = true }
Expand Down
6 changes: 6 additions & 0 deletions rust/crates/sift_stream_bindings/sift_stream_bindings.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ class IngestWithConfigDataChannelValuePy:
@staticmethod
def bitfield(value:typing.Sequence[builtins.int]) -> IngestWithConfigDataChannelValuePy: ...
@staticmethod
def bytes(value:typing.Sequence[builtins.int]) -> IngestWithConfigDataChannelValuePy: ...
@staticmethod
def empty() -> IngestWithConfigDataChannelValuePy: ...

@typing.final
Expand Down Expand Up @@ -798,6 +800,8 @@ class ValuePy:
def Enum(value:typing.Optional[builtins.int]) -> ValuePy: ...
@staticmethod
def BitField(value:typing.Optional[typing.Sequence[builtins.int]]) -> ValuePy: ...
@staticmethod
def Bytes(value:typing.Optional[typing.Sequence[builtins.int]]) -> ValuePy: ...
def is_empty(self) -> builtins.bool: ...
def is_bool(self) -> builtins.bool: ...
def is_string(self) -> builtins.bool: ...
Expand All @@ -809,6 +813,7 @@ class ValuePy:
def is_uint64(self) -> builtins.bool: ...
def is_enum(self) -> builtins.bool: ...
def is_bitfield(self) -> builtins.bool: ...
def is_bytes(self) -> builtins.bool: ...
def as_bool(self) -> builtins.bool: ...
def as_string(self) -> builtins.str: ...
def as_float(self) -> builtins.float: ...
Expand All @@ -819,6 +824,7 @@ class ValuePy:
def as_uint64(self) -> builtins.int: ...
def as_enum(self) -> builtins.int: ...
def as_bitfield(self) -> builtins.list[builtins.int]: ...
def as_bytes(self) -> builtins.list[builtins.int]: ...

@typing.final
class ChannelDataTypePy(Enum):
Expand Down
25 changes: 25 additions & 0 deletions rust/crates/sift_stream_bindings/src/stream/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ impl From<Value> for ChannelValueTypePy {
Value::Uint64(val) => Self::uint64(val),
Value::Enum(val) => Self::enum_value(val),
Value::BitField(val) => Self::bitfield(val),
Value::Bytes(val) => Self::bytes(val),
}
}
}
Expand Down Expand Up @@ -327,6 +328,17 @@ impl ValuePy {
}
}

#[staticmethod]
#[allow(non_snake_case)]
pub fn Bytes(value: Option<Vec<u8>>) -> Self {
match value {
Some(value) => Self {
inner: Value::Bytes(value),
},
None => Self::Empty(),
}
}

pub fn is_empty(&self) -> bool {
matches!(self.inner, Value::Empty)
}
Expand Down Expand Up @@ -371,6 +383,10 @@ impl ValuePy {
matches!(self.inner, Value::BitField(_))
}

pub fn is_bytes(&self) -> bool {
matches!(self.inner, Value::Bytes(_))
}

pub fn as_bool(&self) -> PyResult<bool> {
match &self.inner {
Value::Bool(v) => Ok(*v),
Expand Down Expand Up @@ -460,6 +476,15 @@ impl ValuePy {
)),
}
}

pub fn as_bytes(&self) -> PyResult<Vec<u8>> {
match &self.inner {
Value::Bytes(v) => Ok(v.clone()),
_ => Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"Value is not Bytes",
)),
}
}
}

#[gen_stub_pymethods]
Expand Down
7 changes: 7 additions & 0 deletions rust/crates/sift_stream_bindings/src/stream/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ impl IngestWithConfigDataChannelValuePy {
}
}

#[staticmethod]
pub fn bytes(value: Vec<u8>) -> Self {
Self {
ty: ChannelValueTypePy::bytes(value),
}
}

#[staticmethod]
pub fn empty() -> Self {
Self {
Expand Down
Loading