| // 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. |
| |
| use bytes::Bytes; |
| use iggy::prelude::{ |
| IggyMessage as RustIggyMessage, IggyMessageHeader, |
| SendMessagesConfirmationResponse as RustSendMessagesConfirmationResponse, |
| SendMessagesResponse as RustSendMessagesResponse, |
| }; |
| use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes}; |
| use pyo3_stub_gen::{ |
| derive::{gen_stub_pyclass, gen_stub_pymethods}, |
| impl_stub_type, |
| }; |
| |
| use crate::user_headers::py_user_headers_to_rust; |
| |
| /// A Python class representing a message to be sent. |
| /// This class wraps a Rust message meant for sending, facilitating |
| /// the creation of such messages from Python and their subsequent use in Rust. |
| #[pyclass(from_py_object)] |
| #[gen_stub_pyclass] |
| pub struct SendMessage { |
| pub(crate) inner: RustIggyMessage, |
| } |
| |
| impl Clone for SendMessage { |
| fn clone(&self) -> Self { |
| Self { |
| inner: RustIggyMessage { |
| header: IggyMessageHeader { |
| checksum: self.inner.header.checksum, |
| id: self.inner.header.id, |
| offset: self.inner.header.offset, |
| timestamp: self.inner.header.timestamp, |
| origin_timestamp: self.inner.header.origin_timestamp, |
| user_headers_length: self.inner.header.user_headers_length, |
| payload_length: self.inner.header.payload_length, |
| reserved: self.inner.header.reserved, |
| }, |
| payload: self.inner.payload.clone(), |
| user_headers: self.inner.user_headers.clone(), |
| }, |
| } |
| } |
| } |
| |
| #[gen_stub_pymethods] |
| #[pymethods] |
| impl SendMessage { |
| /// Constructs a new `SendMessage` instance from a string or bytes. |
| /// This method allows for the creation of a `SendMessage` instance |
| /// directly from Python using the provided string or bytes data. |
| #[new] |
| #[pyo3(signature = (data, user_headers=None, id=None))] |
| pub fn new( |
| py: Python, |
| data: PyMessagePayload, |
| #[gen_stub(override_type(type_repr = "dict | None"))] user_headers: Option< |
| &Bound<'_, PyAny>, |
| >, |
| #[gen_stub(override_type(type_repr = "builtins.int | None"))] id: Option<u128>, |
| ) -> PyResult<Self> { |
| let payload = match data { |
| PyMessagePayload::String(data) => Bytes::from(data), |
| PyMessagePayload::Bytes(data) => Bytes::from(data.extract::<Vec<u8>>(py)?), |
| }; |
| let user_headers = user_headers |
| .map(|headers| py_user_headers_to_rust(py, headers)) |
| .transpose()?; |
| let inner = RustIggyMessage::builder() |
| .maybe_id(id) |
| .payload(payload) |
| .maybe_user_headers(user_headers) |
| .build() |
| .map_err(to_value_error)?; |
| Ok(Self { inner }) |
| } |
| } |
| |
| fn to_value_error(error: impl ToString) -> PyErr { |
| PyValueError::new_err(error.to_string()) |
| } |
| |
| #[derive(FromPyObject, IntoPyObject)] |
| pub enum PyMessagePayload { |
| #[pyo3(transparent, annotation = "str")] |
| String(String), |
| #[pyo3(transparent, annotation = "bytes")] |
| Bytes(Py<PyBytes>), |
| } |
| impl_stub_type!(PyMessagePayload = String | PyBytes); |
| |
| /// A Python class representing the commit confirmation for one partition |
| /// written by a send. |
| #[pyclass] |
| #[gen_stub_pyclass] |
| pub struct SendMessagesConfirmation { |
| pub(crate) inner: RustSendMessagesConfirmationResponse, |
| } |
| |
| impl From<&RustSendMessagesConfirmationResponse> for SendMessagesConfirmation { |
| fn from(confirmation: &RustSendMessagesConfirmationResponse) -> Self { |
| Self { |
| inner: confirmation.clone(), |
| } |
| } |
| } |
| |
| #[gen_stub_pymethods] |
| #[pymethods] |
| impl SendMessagesConfirmation { |
| /// Gets the unique identifier (numeric) of the stream the batch was written to. |
| #[getter] |
| pub fn stream_id(&self) -> u32 { |
| self.inner.stream_id |
| } |
| |
| /// Gets the unique identifier (numeric) of the topic the batch was written to. |
| #[getter] |
| pub fn topic_id(&self) -> u32 { |
| self.inner.topic_id |
| } |
| |
| /// Gets the identifier of the partition the batch was written to. |
| #[getter] |
| pub fn partition_id(&self) -> u32 { |
| self.inner.partition_id |
| } |
| |
| /// Gets the offset assigned to the first message of the batch in this partition. |
| /// |
| /// The offset locates the batch, it does not identify it. Delivery is |
| /// at-least-once, so an earlier retry may already have committed these |
| /// messages at a lower offset. |
| /// |
| /// A batch is confirmed once it is committed in memory, not once it is |
| /// fsynced. A crash-restart can stamp a later batch with an offset a client |
| /// has already recorded. |
| /// |
| /// The legacy server confirms nothing, so its confirmation list is empty |
| /// and this value is never reached. |
| #[getter] |
| pub fn base_offset(&self) -> u64 { |
| self.inner.base_offset |
| } |
| } |
| |
| /// A Python class representing the outcome of a successful send. |
| #[pyclass] |
| #[gen_stub_pyclass] |
| pub struct SendMessagesResponse { |
| pub(crate) inner: RustSendMessagesResponse, |
| } |
| |
| impl From<RustSendMessagesResponse> for SendMessagesResponse { |
| fn from(response: RustSendMessagesResponse) -> Self { |
| Self { inner: response } |
| } |
| } |
| |
| #[gen_stub_pymethods] |
| #[pymethods] |
| impl SendMessagesResponse { |
| /// Gets the commit confirmations, one per partition the batch was written to. |
| /// |
| /// The list is empty when the server reports no offsets, and the legacy |
| /// server never reports any, so branch on it being empty rather than |
| /// indexing into it. |
| /// |
| /// A reported `base_offset` never implies uniqueness, because delivery is |
| /// at-least-once and an earlier retry may already have committed the same |
| /// messages at a lower offset. A batch is confirmed once it is committed in |
| /// memory, not once it is fsynced. A crash-restart can stamp a later batch |
| /// with an offset a client has already recorded. |
| #[getter] |
| pub fn confirmations(&self) -> Vec<SendMessagesConfirmation> { |
| self.inner |
| .confirmations |
| .iter() |
| .map(SendMessagesConfirmation::from) |
| .collect() |
| } |
| } |