This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 75a3f4064 feat(python): add message_expiry parameter to create_topic
(#2671)
75a3f4064 is described below
commit 75a3f40642c5735d82cd26ed8c24003201c4d311
Author: Hubert Gruszecki <[email protected]>
AuthorDate: Wed Feb 4 09:45:26 2026 +0100
feat(python): add message_expiry parameter to create_topic (#2671)
The Python SDK's create_topic method lacked the ability to set message
expiration, forcing users to rely on server defaults with no override.
Add optional message_expiry parameter accepting datetime.timedelta.
When provided, converts to IggyExpiry::ExpireDuration; otherwise uses
ServerDefault to inherit from server configuration.
Also introduces version sync tooling between Cargo.toml and
pyproject.toml with CI check and pre-commit hook that auto-fixes
by promoting to the newer version + adds precommit hook for
python formatter.
---
.github/workflows/_common.yml | 19 ++++
.pre-commit-config.yaml | 18 ++++
foreign/python/Cargo.toml | 2 +-
foreign/python/apache_iggy.pyi | 3 +-
foreign/python/pyproject.toml | 2 +-
foreign/python/src/client.rs | 15 ++-
scripts/ci/python-version-sync.sh | 205 ++++++++++++++++++++++++++++++++++++++
7 files changed, 258 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/_common.yml b/.github/workflows/_common.yml
index 43f3ffd65..87e809f72 100644
--- a/.github/workflows/_common.yml
+++ b/.github/workflows/_common.yml
@@ -39,6 +39,15 @@ jobs:
- name: Check Rust versions are synchronized
run: ./scripts/ci/sync-rust-version.sh --check
+ python-versions:
+ name: Check Python SDK versions sync
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Check Python SDK versions are synchronized
+ run: ./scripts/ci/python-version-sync.sh --check
+
pr-title:
name: Check PR Title
if: github.event_name == 'pull_request' && !inputs.skip_pr_title
@@ -215,6 +224,7 @@ jobs:
needs:
[
rust-versions,
+ python-versions,
pr-title,
license-headers,
license-list,
@@ -252,6 +262,7 @@ jobs:
# Always-run checks
RUST_VERSIONS="${{ needs.rust-versions.result }}"
+ PYTHON_VERSIONS="${{ needs.python-versions.result }}"
LICENSE_HEADERS="${{ needs.license-headers.result }}"
LICENSE_LIST="${{ needs.license-list.result }}"
MARKDOWN="${{ needs.markdown.result }}"
@@ -264,6 +275,14 @@ jobs:
echo "| ⏭️ Rust Versions | $RUST_VERSIONS | Check skipped |" >>
$GITHUB_STEP_SUMMARY
fi
+ if [ "$PYTHON_VERSIONS" = "success" ]; then
+ echo "| ✅ Python SDK Versions | success | Cargo.toml and
pyproject.toml synchronized |" >> $GITHUB_STEP_SUMMARY
+ elif [ "$PYTHON_VERSIONS" = "failure" ]; then
+ echo "| ❌ Python SDK Versions | failure | Version mismatch between
Cargo.toml and pyproject.toml |" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "| ⏭️ Python SDK Versions | $PYTHON_VERSIONS | Check skipped
|" >> $GITHUB_STEP_SUMMARY
+ fi
+
if [ "$LICENSE_HEADERS" = "success" ]; then
echo "| ✅ License Headers | success | All files have Apache
headers |" >> $GITHUB_STEP_SUMMARY
elif [ "$LICENSE_HEADERS" = "failure" ]; then
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1a6653300..9aa44c45b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -27,6 +27,16 @@ repos:
exclude: ^helm/charts/.*/templates/
- id: mixed-line-ending
+ # Python SDK formatting
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.15.0
+ hooks:
+ - id: ruff-check
+ args: [--fix]
+ files: ^foreign/python/.*\.(py|pyi)$
+ - id: ruff-format
+ files: ^foreign/python/.*\.(py|pyi)$
+
# CI scripts
- repo: local
hooks:
@@ -69,6 +79,14 @@ repos:
files: ^rust-toolchain\.toml$
pass_filenames: false
+ - id: python-version-sync
+ name: python sdk version sync
+ entry: ./scripts/ci/python-version-sync.sh
+ args: ["--fix"]
+ language: system
+ files: ^foreign/python/(Cargo\.toml|pyproject\.toml)$
+ pass_filenames: false
+
- id: trailing-whitespace
name: trailing whitespace
entry: ./scripts/ci/trailing-whitespace.sh
diff --git a/foreign/python/Cargo.toml b/foreign/python/Cargo.toml
index ee1094cda..a2771d225 100644
--- a/foreign/python/Cargo.toml
+++ b/foreign/python/Cargo.toml
@@ -17,7 +17,7 @@
[package]
name = "apache-iggy"
-version = "0.6.3-dev1"
+version = "0.6.4-dev1"
edition = "2021"
authors = [
"Dario Lencina Talarico <[email protected]>",
diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi
index 0e319c5ac..0c0e9a1b3 100644
--- a/foreign/python/apache_iggy.pyi
+++ b/foreign/python/apache_iggy.pyi
@@ -265,8 +265,9 @@ class IggyClient:
name: builtins.str,
partitions_count: builtins.int,
compression_algorithm: typing.Optional[builtins.str] = None,
- topic_id: typing.Optional[builtins.int] = None,
replication_factor: typing.Optional[builtins.int] = None,
+ message_expiry: typing.Optional[datetime.timedelta] = None,
+ max_topic_size: typing.Optional[builtins.int] = None,
) -> collections.abc.Awaitable[None]:
r"""
Creates a new topic with the given parameters.
diff --git a/foreign/python/pyproject.toml b/foreign/python/pyproject.toml
index 3e5ecd1f0..368cd202f 100644
--- a/foreign/python/pyproject.toml
+++ b/foreign/python/pyproject.toml
@@ -22,7 +22,7 @@ build-backend = "maturin"
[project]
name = "apache-iggy"
requires-python = ">=3.10"
-version = "0.6.1.dev1"
+version = "0.6.4.dev1"
description = "Apache Iggy is the persistent message streaming platform
written in Rust, supporting QUIC, TCP and HTTP transport protocols, capable of
processing millions of messages per second."
readme = "README.md"
license = { file = "LICENSE" }
diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs
index b903329e2..c849b8910 100644
--- a/foreign/python/src/client.rs
+++ b/foreign/python/src/client.rs
@@ -175,7 +175,7 @@ impl IggyClient {
///
/// Returns Ok(()) on successful topic creation or a PyRuntimeError on
failure.
#[pyo3(
- signature = (stream, name, partitions_count, compression_algorithm =
None, replication_factor = None)
+ signature = (stream, name, partitions_count, compression_algorithm =
None, replication_factor = None, message_expiry = None, max_topic_size = None)
)]
#[allow(clippy::too_many_arguments)]
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]",
imports=("collections.abc")))]
@@ -187,6 +187,8 @@ impl IggyClient {
partitions_count: u32,
compression_algorithm: Option<String>,
replication_factor: Option<u8>,
+ message_expiry: Option<Py<PyDelta>>,
+ max_topic_size: Option<u64>,
) -> PyResult<Bound<'a, PyAny>> {
let compression_algorithm = match compression_algorithm {
Some(algo) => CompressionAlgorithm::from_str(&algo)
@@ -194,6 +196,13 @@ impl IggyClient {
None => CompressionAlgorithm::default(),
};
+ let expiry = match message_expiry {
+ Some(delta) =>
IggyExpiry::ExpireDuration(py_delta_to_iggy_duration(&delta)),
+ None => IggyExpiry::ServerDefault,
+ };
+
+ let max_size = max_topic_size.map_or(MaxTopicSize::ServerDefault,
MaxTopicSize::from);
+
let stream = Identifier::from(stream);
let inner = self.inner.clone();
@@ -205,8 +214,8 @@ impl IggyClient {
partitions_count,
compression_algorithm,
replication_factor,
- IggyExpiry::NeverExpire,
- MaxTopicSize::ServerDefault,
+ expiry,
+ max_size,
)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError,
_>(format!("{e:?}")))?;
diff --git a/scripts/ci/python-version-sync.sh
b/scripts/ci/python-version-sync.sh
new file mode 100755
index 000000000..7abc966d0
--- /dev/null
+++ b/scripts/ci/python-version-sync.sh
@@ -0,0 +1,205 @@
+#!/bin/bash
+# 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.
+
+set -euo pipefail
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Default mode
+MODE=""
+
+# Parse arguments
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --check)
+ MODE="check"
+ shift
+ ;;
+ --fix)
+ MODE="fix"
+ shift
+ ;;
+ --help|-h)
+ echo "Usage: $0 [--check|--fix]"
+ echo ""
+ echo "Sync Python SDK version between Cargo.toml and
pyproject.toml"
+ echo ""
+ echo "Options:"
+ echo " --check Check if versions are synchronized"
+ echo " --fix Update the older version to match the newer one"
+ echo " --help Show this help message"
+ exit 0
+ ;;
+ *)
+ echo -e "${RED}Error: Unknown option $1${NC}"
+ echo "Use --help for usage information"
+ exit 1
+ ;;
+ esac
+done
+
+# Require mode to be specified
+if [ -z "$MODE" ]; then
+ echo -e "${RED}Error: Please specify either --check or --fix${NC}"
+ echo "Use --help for usage information"
+ exit 1
+fi
+
+# Get the repository root (two levels up from scripts/ci/)
+REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+cd "$REPO_ROOT"
+
+CARGO_TOML="foreign/python/Cargo.toml"
+PYPROJECT_TOML="foreign/python/pyproject.toml"
+
+# Extract version from Cargo.toml
+CARGO_VERSION=$(grep '^version = ' "$CARGO_TOML" | head -1 | sed 's/version =
"\(.*\)"/\1/')
+
+# Extract version from pyproject.toml
+PYPROJECT_VERSION=$(grep '^version = ' "$PYPROJECT_TOML" | head -1 | sed
's/version = "\(.*\)"/\1/')
+
+if [ -z "$CARGO_VERSION" ]; then
+ echo -e "${RED}Error: Could not extract version from $CARGO_TOML${NC}"
+ exit 1
+fi
+
+if [ -z "$PYPROJECT_VERSION" ]; then
+ echo -e "${RED}Error: Could not extract version from $PYPROJECT_TOML${NC}"
+ exit 1
+fi
+
+# Normalize versions for comparison (both to PEP 440 format with .dev)
+normalize_version() {
+ local v="$1"
+ echo "${v//-dev/.dev}"
+}
+
+# Convert PEP 440 format to Cargo format
+to_cargo_format() {
+ local v="$1"
+ echo "${v//.dev/-dev}"
+}
+
+# Compare two versions, returns:
+# 0 if equal
+# 1 if first is greater
+# 2 if second is greater
+compare_versions() {
+ local v1="$1"
+ local v2="$2"
+
+ # Normalize both versions
+ v1=$(normalize_version "$v1")
+ v2=$(normalize_version "$v2")
+
+ if [ "$v1" = "$v2" ]; then
+ echo 0
+ return
+ fi
+
+ # Extract base version and dev number
+ local base1 dev1 base2 dev2
+ if [[ "$v1" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(\.dev([0-9]+))?$ ]]; then
+ base1="${BASH_REMATCH[1]}"
+ dev1="${BASH_REMATCH[3]:-0}"
+ else
+ base1="$v1"
+ dev1="0"
+ fi
+
+ if [[ "$v2" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(\.dev([0-9]+))?$ ]]; then
+ base2="${BASH_REMATCH[1]}"
+ dev2="${BASH_REMATCH[3]:-0}"
+ else
+ base2="$v2"
+ dev2="0"
+ fi
+
+ # Compare base versions using sort -V
+ local sorted
+ sorted=$(printf '%s\n%s' "$base1" "$base2" | sort -V | head -1)
+
+ if [ "$base1" != "$base2" ]; then
+ if [ "$sorted" = "$base1" ]; then
+ echo 2 # v2 is greater
+ else
+ echo 1 # v1 is greater
+ fi
+ return
+ fi
+
+ # Base versions are equal, compare dev numbers
+ if [ "$dev1" -gt "$dev2" ]; then
+ echo 1
+ elif [ "$dev1" -lt "$dev2" ]; then
+ echo 2
+ else
+ echo 0
+ fi
+}
+
+CARGO_NORMALIZED=$(normalize_version "$CARGO_VERSION")
+PYPROJECT_NORMALIZED=$(normalize_version "$PYPROJECT_VERSION")
+
+echo "Python SDK version check:"
+echo " Cargo.toml: $CARGO_VERSION (normalized: $CARGO_NORMALIZED)"
+echo " pyproject.toml: $PYPROJECT_VERSION"
+echo ""
+
+if [ "$MODE" = "check" ]; then
+ if [ "$CARGO_NORMALIZED" = "$PYPROJECT_NORMALIZED" ]; then
+ echo -e "${GREEN}✓ Python SDK versions are synchronized${NC}"
+ exit 0
+ else
+ echo -e "${RED}✗ Python SDK versions are NOT synchronized${NC}"
+ echo ""
+ echo "Please ensure both files have the same version:"
+ echo " - $CARGO_TOML: use format like '0.6.4-dev1'"
+ echo " - $PYPROJECT_TOML: use format like '0.6.4.dev1'"
+ echo ""
+ echo -e "${YELLOW}Run '$0 --fix' to fix this automatically${NC}"
+ exit 1
+ fi
+elif [ "$MODE" = "fix" ]; then
+ if [ "$CARGO_NORMALIZED" = "$PYPROJECT_NORMALIZED" ]; then
+ echo -e "${GREEN}✓ Python SDK versions are already synchronized${NC}"
+ exit 0
+ fi
+
+ COMPARISON=$(compare_versions "$CARGO_VERSION" "$PYPROJECT_VERSION")
+
+ if [ "$COMPARISON" = "1" ]; then
+ # Cargo version is newer, update pyproject.toml
+ NEW_PYPROJECT_VERSION="$CARGO_NORMALIZED"
+ echo -e "${YELLOW}Cargo.toml has newer version, updating
pyproject.toml...${NC}"
+ sed -i "s/^version = \"$PYPROJECT_VERSION\"/version =
\"$NEW_PYPROJECT_VERSION\"/" "$PYPROJECT_TOML"
+ echo -e "${GREEN}✓ Updated $PYPROJECT_TOML: $PYPROJECT_VERSION ->
$NEW_PYPROJECT_VERSION${NC}"
+ elif [ "$COMPARISON" = "2" ]; then
+ # pyproject version is newer, update Cargo.toml
+ NEW_CARGO_VERSION=$(to_cargo_format "$PYPROJECT_NORMALIZED")
+ echo -e "${YELLOW}pyproject.toml has newer version, updating
Cargo.toml...${NC}"
+ sed -i "s/^version = \"$CARGO_VERSION\"/version =
\"$NEW_CARGO_VERSION\"/" "$CARGO_TOML"
+ echo -e "${GREEN}✓ Updated $CARGO_TOML: $CARGO_VERSION ->
$NEW_CARGO_VERSION${NC}"
+ fi
+
+ exit 0
+fi