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

Reply via email to