diff --git a/.github/actions/python-maturin/pre-merge/action.yml b/.github/actions/python-maturin/pre-merge/action.yml index f97462f34d..6a28d0e5be 100644 --- a/.github/actions/python-maturin/pre-merge/action.yml +++ b/.github/actions/python-maturin/pre-merge/action.yml @@ -20,7 +20,7 @@ description: Python pre-merge testing with maturin github iggy actions inputs: task: - description: "Task to run (lint, test, build)" + description: "Task to run (lint, test)" required: true runs: @@ -90,6 +90,39 @@ runs: echo "pyrefly version: $(uv run pyrefly --version)" shell: bash + - name: Build Python wheel + if: inputs.task == 'test' + run: | + cd foreign/python + + # Build the module + echo "Building Python wheel..." + uv run maturin build -o dist + + # List built artifacts + echo "" + echo "Build artifacts:" + ls -la dist/ + shell: bash + + - name: Check apache_iggy.pyi is up to date + if: inputs.task == 'test' + run: | + # stub_gen must run from foreign/python; a subdirectory corrupts the stub. + cd foreign/python + cargo run --bin stub_gen + + # The tracked stub is post-ruff. Format first: the raw output leaves + # whitespace on blank lines, which `check` reports but only `format` fixes. + uv run --no-sync ruff format apache_iggy.pyi + uv run --no-sync ruff check --fix apache_iggy.pyi + + if ! git diff --exit-code -- apache_iggy.pyi; then + echo "::error::apache_iggy.pyi is out of date. Run 'cargo run --bin stub_gen' from foreign/python, let the ruff hooks format it, and commit the result." + exit 1 + fi + shell: bash + - name: Build Python wheel with coverage instrumentation if: inputs.task == 'test' run: | @@ -105,21 +138,6 @@ runs: uv run --no-sync maturin develop shell: bash - - name: Build Python wheel - if: inputs.task == 'build' - run: | - cd foreign/python - - # Build the module - echo "Building Python wheel..." - uv run maturin build -o dist - - # List built artifacts - echo "" - echo "Build artifacts:" - ls -la dist/ - shell: bash - - name: Build server Docker image for TLS tests if: inputs.task == 'test' run: | diff --git a/.github/config/components.yml b/.github/config/components.yml index 5fef0e88a0..082aec83b5 100644 --- a/.github/config/components.yml +++ b/.github/config/components.yml @@ -232,7 +232,7 @@ components: - "ci-infrastructure" # CI changes trigger full regression paths: - "foreign/python/**" - tasks: ["lint", "test", "build"] + tasks: ["lint", "test"] sdk-php: depends_on: diff --git a/foreign/python/README.md b/foreign/python/README.md index dbbdbe02ca..e2a4fffc2d 100644 --- a/foreign/python/README.md +++ b/foreign/python/README.md @@ -92,12 +92,11 @@ Every installation below compiles the Rust extension, so you'll need: pytest tests/ -v # make sure iggy-server is running and the venv is activated ``` -4. To update the stubs, only after changing the pyo3 API surface (nothing in CI checks stub freshness, so unconditional regen just invites `.pyi` churn), use +4. To update the stubs, after changing the pyo3 API surface, use ```bash # run from foreign/python cargo run --bin stub_gen - # TODO: Known bug: running this from a subdirectory of `foreign/python` corrupts the tracked stub, see https://github.com/apache/iggy/pull/3825/changes/BASE..773a27971b4ddb7b44773ded395ed23afb1de4c9#r3727691619 ``` 5. Before committing, test the pre-commit and pre-push hooks. `prek` only inspects staged content, so stage your work first: diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index 254b23f41c..6c3715b54b 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -872,6 +872,27 @@ class IggyClient: Sends a ping request to the server to check connectivity. Raises `RuntimeError` if the connection fails. """ + def describe_options( + self, scope: builtins.str + ) -> collections.abc.Awaitable[list[OptionSpec]]: + r""" + Describe the option catalog for a resource scope. + + This is the discovery surface for the `options` argument on + `create_topic`/`update_topic`: a key outside the catalog is refused at + create, and the binary transports carry only the error code back. + + Args: + scope: One of `"topic"`, `"stream"`, `"user"`. + + Returns: + An awaitable that resolves to `list[OptionSpec]`, empty for a scope + with no keys yet. + + Raises: + ValueError: If the scope name is not one of the three above. + RuntimeError: If the request fails. + """ def login_user( self, username: builtins.str, password: builtins.str ) -> collections.abc.Awaitable[None]: @@ -1034,27 +1055,6 @@ class IggyClient: Returns the stream details, or `None` if the stream does not exist. Raises `RuntimeError` on failure. """ - def describe_options( - self, scope: builtins.str - ) -> collections.abc.Awaitable[builtins.list[OptionSpec]]: - r""" - Describe the option catalog for a resource scope. - - This is the discovery surface for the `options` argument on - `create_topic`/`update_topic`: a key outside the catalog is refused at - create, and the binary transports carry only the error code back. - - Args: - scope: One of `"topic"`, `"stream"`, `"user"`. - - Returns: - An awaitable that resolves to `list[OptionSpec]`, empty for a scope - with no keys yet. - - Raises: - ValueError: If the scope name is not one of the three above. - RuntimeError: If the request fails. - """ def create_topic( self, stream: builtins.str | builtins.int, @@ -2103,8 +2103,8 @@ class Topic: r""" Options admission resolved for the keys the client did not send. - Same shape as `options`. These would have resolved differently under - another server configuration. + Same shape as `options`. These would have resolved differently + under another server configuration. """ @typing.final @@ -2168,8 +2168,8 @@ class TopicDetails: r""" Options admission resolved for the keys the client did not send. - Same shape as `options`. These would have resolved differently under - another server configuration. + Same shape as `options`. These would have resolved differently + under another server configuration. """ @property def partitions(self) -> builtins.list[Partition]: diff --git a/foreign/python/src/bin/stub_gen.rs b/foreign/python/src/bin/stub_gen.rs index e9db76059d..82693da1a4 100644 --- a/foreign/python/src/bin/stub_gen.rs +++ b/foreign/python/src/bin/stub_gen.rs @@ -47,14 +47,9 @@ fn main() -> Result<()> { // `stub_info` is a function defined by `define_stub_info_gatherer!` macro. let stub = apache_iggy::client::stub_info()?; stub.generate()?; - let path = Path::new(file!()) - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join("apache_iggy.pyi"); + // Anchor on the manifest dir: `stub.generate()` writes to the crate root, so + // a cwd-relative path leaves the tracked stub without its license header. + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("apache_iggy.pyi"); let mut f = File::open(&path)?; let mut content = LICENSE.as_bytes().to_owned(); f.read_to_end(&mut content)?; diff --git a/foreign/python/src/topic.rs b/foreign/python/src/topic.rs index e5f86facd8..0d6e35c803 100644 --- a/foreign/python/src/topic.rs +++ b/foreign/python/src/topic.rs @@ -254,7 +254,7 @@ impl Topic { /// Options admission resolved for the keys the client did not send. /// - /// Same shape as [`Self::options`]. These would have resolved differently + /// Same shape as `options`. These would have resolved differently /// under another server configuration. #[getter] pub fn derived_options<'a>(&self, py: Python<'a>) -> PyResult> { @@ -345,7 +345,7 @@ impl TopicDetails { /// Options admission resolved for the keys the client did not send. /// - /// Same shape as [`Self::options`]. These would have resolved differently + /// Same shape as `options`. These would have resolved differently /// under another server configuration. #[getter] pub fn derived_options<'a>(&self, py: Python<'a>) -> PyResult> {