From b3a9dc2f3f40963a0d65c4f0ef37c05c49c968b0 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Wed, 21 Oct 2026 16:33:46 +0530 Subject: [PATCH 1/9] feat(python): add snapshot support --- foreign/python/apache_iggy.pyi | 22 +++++++++ foreign/python/src/client.rs | 53 ++++++++++++++++++++- foreign/python/tests/test_snapshot.py | 66 +++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 foreign/python/tests/test_snapshot.py diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index ef519c9305..9d10fcb091 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -1260,6 +1260,28 @@ class IggyClient: authenticated, the user lacks the permission, or the request fails. """ + def snapshot( + self, compression: builtins.str, snapshot_types: typing.Sequence[builtins.str] + ) -> collections.abc.Awaitable[bytes]: + r""" + Capture a diagnostic snapshot of the server as a compressed ZIP archive. + + Args: + compression: Archive compression method. One of `"stored"`, + `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. + snapshot_types: Data to include in the snapshot. Each entry is one + of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, + `"test"`, `"server_logs"`, `"server_config"`, `"all"`. + + Returns: + An awaitable that resolves to the ZIP archive `bytes`. + + Raises: + ValueError: If `compression` is empty or unknown, `snapshot_types` + is empty, or any entry in `snapshot_types` is unknown. + RuntimeError: If the client is not connected, the session is not + authenticated, or the request fails. + """ def describe_options( self, scope: builtins.str ) -> collections.abc.Awaitable[list[OptionSpec]]: diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 8faa778ccd..79a8cbf094 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -30,7 +30,7 @@ use pyo3_async_runtimes::tokio::future_into_py; use pyo3_stub_gen::define_stub_info_gatherer; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use serde::Deserialize; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::fmt::Display; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -315,6 +315,57 @@ impl IggyClient { }) } + /// Capture a diagnostic snapshot of the server as a compressed ZIP archive. + /// + /// Args: + /// compression: Archive compression method. One of `"stored"`, + /// `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. + /// snapshot_types: Data to include in the snapshot. Each entry is one + /// of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, + /// `"test"`, `"server_logs"`, `"server_config"`, `"all"`. + /// + /// Returns: + /// An awaitable that resolves to the ZIP archive `bytes`. + /// + /// Raises: + /// ValueError: If `compression` is empty or unknown, `snapshot_types` + /// is empty, or any entry in `snapshot_types` is unknown. + /// RuntimeError: If the client is not connected, the session is not + /// authenticated, or the request fails. + #[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[bytes]", imports=("collections.abc")))] + fn snapshot<'a>( + &self, + py: Python<'a>, + compression: &str, + snapshot_types: Vec, + ) -> PyResult> { + let compression = match compression.trim() { + "" => return Err(PyValueError::new_err("compression must not be empty")), + value => SnapshotCompression::from_str(value).map_err(to_value_error)?, + }; + if snapshot_types.is_empty() { + return Err(PyValueError::new_err("snapshot_types must not be empty")); + } + + let mut seen_snapshot_types = HashSet::new(); + let snapshot_types = snapshot_types + .into_iter() + .filter(|snapshot_type| seen_snapshot_types.insert(snapshot_type.clone())) + .map(|snapshot_type| { + SystemSnapshotType::from_str(&snapshot_type).map_err(to_value_error) + }) + .collect::>>()?; + + let inner = self.inner.clone(); + future_into_py(py, async move { + let snapshot = inner + .snapshot(compression, snapshot_types) + .await + .map_err(to_runtime_error)?; + Ok(Python::attach(|py| PyBytes::new(py, &snapshot.0).unbind())) + }) + } + /// Describe the option catalog for a resource scope. /// /// This is the discovery surface for the `options` argument on diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py new file mode 100644 index 0000000000..1ba7f5b692 --- /dev/null +++ b/foreign/python/tests/test_snapshot.py @@ -0,0 +1,66 @@ +# 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. + +import io +import zipfile + +import pytest + +from apache_iggy import IggyClient + + +class TestSnapshot: + """Test server snapshot retrieval.""" + + @pytest.mark.asyncio + async def test_snapshot_returns_zip_bytes(self, iggy_client: IggyClient): + """Capturing a snapshot returns a non-empty ZIP archive as bytes.""" + data = await iggy_client.snapshot("deflated", ["all"]) + + assert isinstance(data, bytes) + assert len(data) > 0 + assert zipfile.is_zipfile(io.BytesIO(data)) + + @pytest.mark.unit + @pytest.mark.parametrize("compression", ["", " ", "invalid-compression"]) + def test_snapshot_rejects_invalid_compression(self, compression: str): + """Empty or unknown compression raises ValueError before sending.""" + client = IggyClient("127.0.0.1:1") + with pytest.raises(ValueError): + client.snapshot(compression, ["test"]) + + @pytest.mark.unit + def test_snapshot_rejects_invalid_snapshot_type(self): + """An unknown snapshot type raises ValueError before sending.""" + client = IggyClient("127.0.0.1:1") + with pytest.raises(ValueError): + client.snapshot("deflated", ["not-a-real-type"]) + + @pytest.mark.unit + def test_snapshot_rejects_empty_snapshot_types(self): + """An empty snapshot_types list raises ValueError before sending.""" + client = IggyClient("127.0.0.1:1") + with pytest.raises(ValueError): + client.snapshot("deflated", []) + + @pytest.mark.asyncio + async def test_snapshot_rejects_all_combined_with_other_type( + self, iggy_client: IggyClient + ): + """Combining 'all' with another snapshot type is rejected.""" + with pytest.raises(RuntimeError): + await iggy_client.snapshot("deflated", ["all", "test"]) From 151bb6e58563f54356453673c05894c5463e289e Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Wed, 21 Oct 2026 18:58:26 +0530 Subject: [PATCH 2/9] fix(python): deduplicate parsed snapshot types and update docs --- foreign/python/apache_iggy.pyi | 9 +++++++-- foreign/python/src/client.rs | 28 +++++++++++++++------------ foreign/python/tests/test_snapshot.py | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index 9d10fcb091..770c44fe00 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -1266,12 +1266,16 @@ class IggyClient: r""" Capture a diagnostic snapshot of the server as a compressed ZIP archive. + Requires an authenticated session whose user holds the `manage_servers` + global permission. + Args: compression: Archive compression method. One of `"stored"`, `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. snapshot_types: Data to include in the snapshot. Each entry is one of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, - `"test"`, `"server_logs"`, `"server_config"`, `"all"`. + `"test"`, `"server_logs"`, `"server_config"`, `"all"`. `"all"` + cannot be combined with any other snapshot type. Returns: An awaitable that resolves to the ZIP archive `bytes`. @@ -1280,7 +1284,8 @@ class IggyClient: ValueError: If `compression` is empty or unknown, `snapshot_types` is empty, or any entry in `snapshot_types` is unknown. RuntimeError: If the client is not connected, the session is not - authenticated, or the request fails. + authenticated, the user lacks the permission, `"all"` is + combined with another snapshot type, or the request fails. """ def describe_options( self, scope: builtins.str diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 79a8cbf094..23080b443d 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -30,7 +30,7 @@ use pyo3_async_runtimes::tokio::future_into_py; use pyo3_stub_gen::define_stub_info_gatherer; use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use serde::Deserialize; -use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Display; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -317,12 +317,16 @@ impl IggyClient { /// Capture a diagnostic snapshot of the server as a compressed ZIP archive. /// + /// Requires an authenticated session whose user holds the `manage_servers` + /// global permission. + /// /// Args: /// compression: Archive compression method. One of `"stored"`, /// `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. /// snapshot_types: Data to include in the snapshot. Each entry is one /// of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, - /// `"test"`, `"server_logs"`, `"server_config"`, `"all"`. + /// `"test"`, `"server_logs"`, `"server_config"`, `"all"`. `"all"` + /// cannot be combined with any other snapshot type. /// /// Returns: /// An awaitable that resolves to the ZIP archive `bytes`. @@ -331,7 +335,8 @@ impl IggyClient { /// ValueError: If `compression` is empty or unknown, `snapshot_types` /// is empty, or any entry in `snapshot_types` is unknown. /// RuntimeError: If the client is not connected, the session is not - /// authenticated, or the request fails. + /// authenticated, the user lacks the permission, `"all"` is + /// combined with another snapshot type, or the request fails. #[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[bytes]", imports=("collections.abc")))] fn snapshot<'a>( &self, @@ -347,19 +352,18 @@ impl IggyClient { return Err(PyValueError::new_err("snapshot_types must not be empty")); } - let mut seen_snapshot_types = HashSet::new(); - let snapshot_types = snapshot_types - .into_iter() - .filter(|snapshot_type| seen_snapshot_types.insert(snapshot_type.clone())) - .map(|snapshot_type| { - SystemSnapshotType::from_str(&snapshot_type).map_err(to_value_error) - }) - .collect::>>()?; + let mut parsed_types = Vec::with_capacity(snapshot_types.len()); + for snapshot_type in snapshot_types { + let parsed = SystemSnapshotType::from_str(&snapshot_type).map_err(to_value_error)?; + if !parsed_types.contains(&parsed) { + parsed_types.push(parsed); + } + } let inner = self.inner.clone(); future_into_py(py, async move { let snapshot = inner - .snapshot(compression, snapshot_types) + .snapshot(compression, parsed_types) .await .map_err(to_runtime_error)?; Ok(Python::attach(|py| PyBytes::new(py, &snapshot.0).unbind())) diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index 1ba7f5b692..8bfe7bd5cb 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -62,5 +62,5 @@ async def test_snapshot_rejects_all_combined_with_other_type( self, iggy_client: IggyClient ): """Combining 'all' with another snapshot type is rejected.""" - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError, match="(?i)invalid command"): await iggy_client.snapshot("deflated", ["all", "test"]) From b1ea98d8ac96c594275b5f84fcca7fe67d8d2d8b Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Thu, 22 Oct 2026 11:37:08 +0530 Subject: [PATCH 3/9] fix(python): trim snapshot types and document binary timeout --- foreign/python/apache_iggy.pyi | 8 +++++++- foreign/python/src/client.rs | 8 +++++--- foreign/python/tests/test_snapshot.py | 9 +++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index 770c44fe00..9cfae30e0f 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -1269,6 +1269,11 @@ class IggyClient: Requires an authenticated session whose user holds the `manage_servers` global permission. + Binary transports enforce a 30-second operation deadline. Large + snapshots (such as `"server_logs"` or `"all"`) that exceed 30 seconds + fail with `RuntimeError`, and the server rejects concurrent retries + until the in-flight snapshot collection finishes. + Args: compression: Archive compression method. One of `"stored"`, `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. @@ -1285,7 +1290,8 @@ class IggyClient: is empty, or any entry in `snapshot_types` is unknown. RuntimeError: If the client is not connected, the session is not authenticated, the user lacks the permission, `"all"` is - combined with another snapshot type, or the request fails. + combined with another snapshot type, the 30-second binary + operation deadline expires, or the request fails. """ def describe_options( self, scope: builtins.str diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 23080b443d..f0c5c06988 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -336,7 +336,8 @@ impl IggyClient { /// is empty, or any entry in `snapshot_types` is unknown. /// RuntimeError: If the client is not connected, the session is not /// authenticated, the user lacks the permission, `"all"` is - /// combined with another snapshot type, or the request fails. + /// combined with another snapshot type, the 30-second binary + /// operation deadline expires, or the request fails. #[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[bytes]", imports=("collections.abc")))] fn snapshot<'a>( &self, @@ -352,9 +353,10 @@ impl IggyClient { return Err(PyValueError::new_err("snapshot_types must not be empty")); } - let mut parsed_types = Vec::with_capacity(snapshot_types.len()); + let mut parsed_types = Vec::with_capacity(snapshot_types.len().min(7)); for snapshot_type in snapshot_types { - let parsed = SystemSnapshotType::from_str(&snapshot_type).map_err(to_value_error)?; + let parsed = + SystemSnapshotType::from_str(snapshot_type.trim()).map_err(to_value_error)?; if !parsed_types.contains(&parsed) { parsed_types.push(parsed); } diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index 8bfe7bd5cb..23d1edf7ac 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -64,3 +64,12 @@ async def test_snapshot_rejects_all_combined_with_other_type( """Combining 'all' with another snapshot type is rejected.""" with pytest.raises(RuntimeError, match="(?i)invalid command"): await iggy_client.snapshot("deflated", ["all", "test"]) + + @pytest.mark.asyncio + async def test_snapshot_deduplicates_snapshot_types(self, iggy_client: IggyClient): + """Duplicate snapshot types are deduplicated before encoding.""" + data = await iggy_client.snapshot("deflated", ["test"] * 255 + [" TEST "]) + + assert isinstance(data, bytes) + assert len(data) > 0 + assert zipfile.is_zipfile(io.BytesIO(data)) From 30473541eba4a8be8f6a0743c25a9e4f7e62215c Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Thu, 22 Oct 2026 11:44:45 +0530 Subject: [PATCH 4/9] chore(python): sync snapshot docstring in stub file --- foreign/python/apache_iggy.pyi | 5 ----- 1 file changed, 5 deletions(-) diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index 9cfae30e0f..a8f823b0e9 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -1269,11 +1269,6 @@ class IggyClient: Requires an authenticated session whose user holds the `manage_servers` global permission. - Binary transports enforce a 30-second operation deadline. Large - snapshots (such as `"server_logs"` or `"all"`) that exceed 30 seconds - fail with `RuntimeError`, and the server rejects concurrent retries - until the in-flight snapshot collection finishes. - Args: compression: Archive compression method. One of `"stored"`, `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. From 419e14921739330bc05482ed0969842dc3111611 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Thu, 22 Oct 2026 12:00:50 +0530 Subject: [PATCH 5/9] chore(python): remove redundant capacity cap in snapshot --- foreign/python/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index f0c5c06988..8ba890206a 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -353,7 +353,7 @@ impl IggyClient { return Err(PyValueError::new_err("snapshot_types must not be empty")); } - let mut parsed_types = Vec::with_capacity(snapshot_types.len().min(7)); + let mut parsed_types = Vec::with_capacity(snapshot_types.len()); for snapshot_type in snapshot_types { let parsed = SystemSnapshotType::from_str(snapshot_type.trim()).map_err(to_value_error)?; From 069da087f6452d138193a16e95704c95b86777e3 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Sat, 24 Oct 2026 14:18:51 +0530 Subject: [PATCH 6/9] refactor(python): rename compression to snapshot_compression --- foreign/python/apache_iggy.pyi | 8 +++++--- foreign/python/src/client.rs | 10 +++++----- foreign/python/tests/test_snapshot.py | 10 ++++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index a8f823b0e9..f2ba3b6452 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -1261,7 +1261,9 @@ class IggyClient: fails. """ def snapshot( - self, compression: builtins.str, snapshot_types: typing.Sequence[builtins.str] + self, + snapshot_compression: builtins.str, + snapshot_types: typing.Sequence[builtins.str], ) -> collections.abc.Awaitable[bytes]: r""" Capture a diagnostic snapshot of the server as a compressed ZIP archive. @@ -1270,7 +1272,7 @@ class IggyClient: global permission. Args: - compression: Archive compression method. One of `"stored"`, + snapshot_compression: Archive compression method. One of `"stored"`, `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. snapshot_types: Data to include in the snapshot. Each entry is one of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, @@ -1281,7 +1283,7 @@ class IggyClient: An awaitable that resolves to the ZIP archive `bytes`. Raises: - ValueError: If `compression` is empty or unknown, `snapshot_types` + ValueError: If `snapshot_compression` is empty or unknown, `snapshot_types` is empty, or any entry in `snapshot_types` is unknown. RuntimeError: If the client is not connected, the session is not authenticated, the user lacks the permission, `"all"` is diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 8ba890206a..6174e8f081 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -321,7 +321,7 @@ impl IggyClient { /// global permission. /// /// Args: - /// compression: Archive compression method. One of `"stored"`, + /// snapshot_compression: Archive compression method. One of `"stored"`, /// `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. /// snapshot_types: Data to include in the snapshot. Each entry is one /// of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, @@ -332,7 +332,7 @@ impl IggyClient { /// An awaitable that resolves to the ZIP archive `bytes`. /// /// Raises: - /// ValueError: If `compression` is empty or unknown, `snapshot_types` + /// ValueError: If `snapshot_compression` is empty or unknown, `snapshot_types` /// is empty, or any entry in `snapshot_types` is unknown. /// RuntimeError: If the client is not connected, the session is not /// authenticated, the user lacks the permission, `"all"` is @@ -342,11 +342,11 @@ impl IggyClient { fn snapshot<'a>( &self, py: Python<'a>, - compression: &str, + snapshot_compression: &str, snapshot_types: Vec, ) -> PyResult> { - let compression = match compression.trim() { - "" => return Err(PyValueError::new_err("compression must not be empty")), + let compression = match snapshot_compression.trim() { + "" => return Err(PyValueError::new_err("snapshot_compression must not be empty")), value => SnapshotCompression::from_str(value).map_err(to_value_error)?, }; if snapshot_types.is_empty() { diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index 23d1edf7ac..6fc1733b03 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -36,12 +36,14 @@ async def test_snapshot_returns_zip_bytes(self, iggy_client: IggyClient): assert zipfile.is_zipfile(io.BytesIO(data)) @pytest.mark.unit - @pytest.mark.parametrize("compression", ["", " ", "invalid-compression"]) - def test_snapshot_rejects_invalid_compression(self, compression: str): - """Empty or unknown compression raises ValueError before sending.""" + @pytest.mark.parametrize("snapshot_compression", ["", " ", "invalid-compression"]) + def test_snapshot_rejects_invalid_snapshot_compression( + self, snapshot_compression: str + ): + """Empty or unknown snapshot_compression raises ValueError before sending.""" client = IggyClient("127.0.0.1:1") with pytest.raises(ValueError): - client.snapshot(compression, ["test"]) + client.snapshot(snapshot_compression, ["test"]) @pytest.mark.unit def test_snapshot_rejects_invalid_snapshot_type(self): From 96c5e4d28ea2b08ee44f6d85ab85e53b1d796ff0 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Sat, 24 Oct 2026 16:30:43 +0530 Subject: [PATCH 7/9] refactor(python): use enums for snapshot compression and snapshot types --- foreign/python/apache_iggy.pyi | 90 +++++++++++-- foreign/python/src/client.rs | 37 +++--- foreign/python/src/lib.rs | 4 + foreign/python/src/snapshot.rs | 174 ++++++++++++++++++++++++++ foreign/python/tests/test_snapshot.py | 46 ++++--- 5 files changed, 306 insertions(+), 45 deletions(-) create mode 100644 foreign/python/src/snapshot.rs diff --git a/foreign/python/apache_iggy.pyi b/foreign/python/apache_iggy.pyi index f2ba3b6452..6738076873 100644 --- a/foreign/python/apache_iggy.pyi +++ b/foreign/python/apache_iggy.pyi @@ -108,11 +108,13 @@ __all__ = [ "SendMessage", "SendMessagesConfirmation", "SendMessagesResponse", + "SnapshotCompression", "Stats", "Stream", "StreamDetails", "StreamOptions", "StreamPermissions", + "SystemSnapshotType", "TcpConfig", "TcpReconnectionConfig", "TokenInfo", @@ -1262,8 +1264,8 @@ class IggyClient: """ def snapshot( self, - snapshot_compression: builtins.str, - snapshot_types: typing.Sequence[builtins.str], + snapshot_compression: SnapshotCompression, + snapshot_types: typing.Sequence[SystemSnapshotType], ) -> collections.abc.Awaitable[bytes]: r""" Capture a diagnostic snapshot of the server as a compressed ZIP archive. @@ -1272,21 +1274,21 @@ class IggyClient: global permission. Args: - snapshot_compression: Archive compression method. One of `"stored"`, - `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. - snapshot_types: Data to include in the snapshot. Each entry is one - of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, - `"test"`, `"server_logs"`, `"server_config"`, `"all"`. `"all"` - cannot be combined with any other snapshot type. + snapshot_compression: Archive compression method as a + `SnapshotCompression` enum. + snapshot_types: Data to include in the snapshot as a list of + `SystemSnapshotType` enums. `SystemSnapshotType.All` cannot be + combined with any other snapshot type. Returns: An awaitable that resolves to the ZIP archive `bytes`. Raises: - ValueError: If `snapshot_compression` is empty or unknown, `snapshot_types` - is empty, or any entry in `snapshot_types` is unknown. + TypeError: If `snapshot_compression` is not a `SnapshotCompression` + or `snapshot_types` contains elements other than `SystemSnapshotType`. + ValueError: If `snapshot_types` is empty. RuntimeError: If the client is not connected, the session is not - authenticated, the user lacks the permission, `"all"` is + authenticated, the user lacks the permission, `SystemSnapshotType.All` is combined with another snapshot type, the 30-second binary operation deadline expires, or the request fails. """ @@ -4151,6 +4153,37 @@ class SendMessagesResponse: receipts for persisted topics. """ +@typing.final +class SnapshotCompression: + r""" + Compression method for server snapshots. + """ + + Stored: typing.ClassVar[SnapshotCompression] + r""" + Store the file as is without compression. + """ + Deflated: typing.ClassVar[SnapshotCompression] + r""" + Compress the file using Deflate. + """ + Bzip2: typing.ClassVar[SnapshotCompression] + r""" + Compress the file using BZIP2. + """ + Zstd: typing.ClassVar[SnapshotCompression] + r""" + Compress the file using ZStandard. + """ + Lzma: typing.ClassVar[SnapshotCompression] + r""" + Compress the file using LZMA. + """ + Xz: typing.ClassVar[SnapshotCompression] + r""" + Compress the file using XZ. + """ + @typing.final class Stats: r""" @@ -4606,6 +4639,41 @@ class StreamPermissions: treated as `None`. """ +@typing.final +class SystemSnapshotType: + r""" + Type of system data to include in a snapshot. + """ + + FilesystemOverview: typing.ClassVar[SystemSnapshotType] + r""" + Overview of the filesystem. + """ + ProcessList: typing.ClassVar[SystemSnapshotType] + r""" + List of currently running processes. + """ + ResourceUsage: typing.ClassVar[SystemSnapshotType] + r""" + Resource usage statistics of the system. + """ + Test: typing.ClassVar[SystemSnapshotType] + r""" + Test snapshot type for development purposes. + """ + ServerLogs: typing.ClassVar[SystemSnapshotType] + r""" + Server logs. + """ + ServerConfig: typing.ClassVar[SystemSnapshotType] + r""" + Server configuration. + """ + All: typing.ClassVar[SystemSnapshotType] + r""" + Everything. + """ + @typing.final class TcpConfig: r""" diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 6174e8f081..340610ddaf 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -52,6 +52,9 @@ use crate::producer::{IggyProducer, ProducerMode, u32_param as producer_u32_para use crate::receive_message::{PartitionContext, PollingStrategy, ReceiveMessage}; use crate::resource_options::{OptionKey, StreamOptions, TopicOptions, UserOptions}; use crate::send_message::{SendMessage, SendMessagesResponse as PySendMessagesResponse}; +use crate::snapshot::{ + SnapshotCompression as PySnapshotCompression, SystemSnapshotType as PySystemSnapshotType, +}; use crate::stats::Stats as PyStats; use crate::stream::{Stream, StreamDetails}; use crate::topic::{IggyExpiry, MaxTopicSize, Topic, TopicDetails}; @@ -321,48 +324,44 @@ impl IggyClient { /// global permission. /// /// Args: - /// snapshot_compression: Archive compression method. One of `"stored"`, - /// `"deflated"`, `"bzip2"`, `"zstd"`, `"lzma"`, `"xz"`. - /// snapshot_types: Data to include in the snapshot. Each entry is one - /// of `"filesystem_overview"`, `"process_list"`, `"resource_usage"`, - /// `"test"`, `"server_logs"`, `"server_config"`, `"all"`. `"all"` - /// cannot be combined with any other snapshot type. + /// snapshot_compression: Archive compression method as a + /// `SnapshotCompression` enum. + /// snapshot_types: Data to include in the snapshot as a list of + /// `SystemSnapshotType` enums. `SystemSnapshotType.All` cannot be + /// combined with any other snapshot type. /// /// Returns: /// An awaitable that resolves to the ZIP archive `bytes`. /// /// Raises: - /// ValueError: If `snapshot_compression` is empty or unknown, `snapshot_types` - /// is empty, or any entry in `snapshot_types` is unknown. + /// TypeError: If `snapshot_compression` is not a `SnapshotCompression` + /// or `snapshot_types` contains elements other than `SystemSnapshotType`. + /// ValueError: If `snapshot_types` is empty. /// RuntimeError: If the client is not connected, the session is not - /// authenticated, the user lacks the permission, `"all"` is + /// authenticated, the user lacks the permission, `SystemSnapshotType.All` is /// combined with another snapshot type, the 30-second binary /// operation deadline expires, or the request fails. #[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[bytes]", imports=("collections.abc")))] fn snapshot<'a>( &self, py: Python<'a>, - snapshot_compression: &str, - snapshot_types: Vec, + snapshot_compression: PySnapshotCompression, + snapshot_types: Vec, ) -> PyResult> { - let compression = match snapshot_compression.trim() { - "" => return Err(PyValueError::new_err("snapshot_compression must not be empty")), - value => SnapshotCompression::from_str(value).map_err(to_value_error)?, - }; if snapshot_types.is_empty() { return Err(PyValueError::new_err("snapshot_types must not be empty")); } let mut parsed_types = Vec::with_capacity(snapshot_types.len()); for snapshot_type in snapshot_types { - let parsed = - SystemSnapshotType::from_str(snapshot_type.trim()).map_err(to_value_error)?; - if !parsed_types.contains(&parsed) { - parsed_types.push(parsed); + let rust_type: SystemSnapshotType = snapshot_type.into(); + if !parsed_types.contains(&rust_type) { + parsed_types.push(rust_type); } } let inner = self.inner.clone(); + let compression: SnapshotCompression = snapshot_compression.into(); future_into_py(py, async move { let snapshot = inner .snapshot(compression, parsed_types) diff --git a/foreign/python/src/lib.rs b/foreign/python/src/lib.rs index 9ed4a4f6ce..fed4471a73 100644 --- a/foreign/python/src/lib.rs +++ b/foreign/python/src/lib.rs @@ -29,6 +29,7 @@ mod producer; mod receive_message; mod resource_options; mod send_message; +mod snapshot; mod stats; mod stream; mod topic; @@ -70,6 +71,7 @@ use resource_options::{ encryption_key_fingerprint, }; use send_message::{SendMessage, SendMessagesConfirmation, SendMessagesResponse}; +use snapshot::{SnapshotCompression, SystemSnapshotType}; use stats::{CacheMetrics, CacheMetricsKey, Stats}; use stream::{Stream, StreamDetails}; use topic::{IggyExpiry, MaxTopicSize, Partition, Topic, TopicDetails}; @@ -146,6 +148,8 @@ fn apache_iggy(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/foreign/python/src/snapshot.rs b/foreign/python/src/snapshot.rs new file mode 100644 index 0000000000..8e5f9658a9 --- /dev/null +++ b/foreign/python/src/snapshot.rs @@ -0,0 +1,174 @@ +// 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 iggy::prelude::{ + SnapshotCompression as RustSnapshotCompression, SystemSnapshotType as RustSystemSnapshotType, +}; +use pyo3::prelude::*; +use pyo3_stub_gen::derive::gen_stub_pyclass_enum; + +/// Compression method for server snapshots. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[gen_stub_pyclass_enum] +#[pyclass(eq, from_py_object)] +pub enum SnapshotCompression { + /// Store the file as is without compression. + Stored, + /// Compress the file using Deflate. + Deflated, + /// Compress the file using BZIP2. + Bzip2, + /// Compress the file using ZStandard. + Zstd, + /// Compress the file using LZMA. + Lzma, + /// Compress the file using XZ. + Xz, +} + +impl From for RustSnapshotCompression { + fn from(compression: SnapshotCompression) -> Self { + match compression { + SnapshotCompression::Stored => RustSnapshotCompression::Stored, + SnapshotCompression::Deflated => RustSnapshotCompression::Deflated, + SnapshotCompression::Bzip2 => RustSnapshotCompression::Bzip2, + SnapshotCompression::Zstd => RustSnapshotCompression::Zstd, + SnapshotCompression::Lzma => RustSnapshotCompression::Lzma, + SnapshotCompression::Xz => RustSnapshotCompression::Xz, + } + } +} + +impl From for SnapshotCompression { + fn from(compression: RustSnapshotCompression) -> Self { + match compression { + RustSnapshotCompression::Stored => SnapshotCompression::Stored, + RustSnapshotCompression::Deflated => SnapshotCompression::Deflated, + RustSnapshotCompression::Bzip2 => SnapshotCompression::Bzip2, + RustSnapshotCompression::Zstd => SnapshotCompression::Zstd, + RustSnapshotCompression::Lzma => SnapshotCompression::Lzma, + RustSnapshotCompression::Xz => SnapshotCompression::Xz, + } + } +} + +/// Type of system data to include in a snapshot. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[gen_stub_pyclass_enum] +#[pyclass(eq, from_py_object)] +pub enum SystemSnapshotType { + /// Overview of the filesystem. + FilesystemOverview, + /// List of currently running processes. + ProcessList, + /// Resource usage statistics of the system. + ResourceUsage, + /// Test snapshot type for development purposes. + Test, + /// Server logs. + ServerLogs, + /// Server configuration. + ServerConfig, + /// Everything. + All, +} + +impl From for RustSystemSnapshotType { + fn from(snapshot_type: SystemSnapshotType) -> Self { + match snapshot_type { + SystemSnapshotType::FilesystemOverview => RustSystemSnapshotType::FilesystemOverview, + SystemSnapshotType::ProcessList => RustSystemSnapshotType::ProcessList, + SystemSnapshotType::ResourceUsage => RustSystemSnapshotType::ResourceUsage, + SystemSnapshotType::Test => RustSystemSnapshotType::Test, + SystemSnapshotType::ServerLogs => RustSystemSnapshotType::ServerLogs, + SystemSnapshotType::ServerConfig => RustSystemSnapshotType::ServerConfig, + SystemSnapshotType::All => RustSystemSnapshotType::All, + } + } +} + +impl From for SystemSnapshotType { + fn from(snapshot_type: RustSystemSnapshotType) -> Self { + match snapshot_type { + RustSystemSnapshotType::FilesystemOverview => SystemSnapshotType::FilesystemOverview, + RustSystemSnapshotType::ProcessList => SystemSnapshotType::ProcessList, + RustSystemSnapshotType::ResourceUsage => SystemSnapshotType::ResourceUsage, + RustSystemSnapshotType::Test => SystemSnapshotType::Test, + RustSystemSnapshotType::ServerLogs => SystemSnapshotType::ServerLogs, + RustSystemSnapshotType::ServerConfig => SystemSnapshotType::ServerConfig, + RustSystemSnapshotType::All => SystemSnapshotType::All, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn snapshot_compression_converts_bidirectionally() { + let compressions = [ + (SnapshotCompression::Stored, RustSnapshotCompression::Stored), + ( + SnapshotCompression::Deflated, + RustSnapshotCompression::Deflated, + ), + (SnapshotCompression::Bzip2, RustSnapshotCompression::Bzip2), + (SnapshotCompression::Zstd, RustSnapshotCompression::Zstd), + (SnapshotCompression::Lzma, RustSnapshotCompression::Lzma), + (SnapshotCompression::Xz, RustSnapshotCompression::Xz), + ]; + + for (py_comp, rust_comp) in compressions { + assert_eq!(RustSnapshotCompression::from(py_comp), rust_comp); + assert_eq!(SnapshotCompression::from(rust_comp), py_comp); + } + } + + #[test] + fn system_snapshot_type_converts_bidirectionally() { + let types = [ + ( + SystemSnapshotType::FilesystemOverview, + RustSystemSnapshotType::FilesystemOverview, + ), + ( + SystemSnapshotType::ProcessList, + RustSystemSnapshotType::ProcessList, + ), + ( + SystemSnapshotType::ResourceUsage, + RustSystemSnapshotType::ResourceUsage, + ), + (SystemSnapshotType::Test, RustSystemSnapshotType::Test), + ( + SystemSnapshotType::ServerLogs, + RustSystemSnapshotType::ServerLogs, + ), + ( + SystemSnapshotType::ServerConfig, + RustSystemSnapshotType::ServerConfig, + ), + (SystemSnapshotType::All, RustSystemSnapshotType::All), + ]; + + for (py_type, rust_type) in types { + assert_eq!(RustSystemSnapshotType::from(py_type), rust_type); + assert_eq!(SystemSnapshotType::from(rust_type), py_type); + } + } +} diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index 6fc1733b03..483e35d3f8 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -20,7 +20,11 @@ import pytest -from apache_iggy import IggyClient +from apache_iggy import ( + IggyClient, + SnapshotCompression, + SystemSnapshotType, +) class TestSnapshot: @@ -29,35 +33,35 @@ class TestSnapshot: @pytest.mark.asyncio async def test_snapshot_returns_zip_bytes(self, iggy_client: IggyClient): """Capturing a snapshot returns a non-empty ZIP archive as bytes.""" - data = await iggy_client.snapshot("deflated", ["all"]) + data = await iggy_client.snapshot( + SnapshotCompression.Deflated, [SystemSnapshotType.All] + ) assert isinstance(data, bytes) assert len(data) > 0 assert zipfile.is_zipfile(io.BytesIO(data)) @pytest.mark.unit - @pytest.mark.parametrize("snapshot_compression", ["", " ", "invalid-compression"]) - def test_snapshot_rejects_invalid_snapshot_compression( - self, snapshot_compression: str - ): - """Empty or unknown snapshot_compression raises ValueError before sending.""" + @pytest.mark.parametrize("invalid_compression", ["deflated", "", 123, None]) + def test_snapshot_rejects_invalid_snapshot_compression(self, invalid_compression): + """Passing non-enum snapshot_compression raises TypeError before sending.""" client = IggyClient("127.0.0.1:1") - with pytest.raises(ValueError): - client.snapshot(snapshot_compression, ["test"]) + with pytest.raises(TypeError): + client.snapshot(invalid_compression, [SystemSnapshotType.Test]) @pytest.mark.unit def test_snapshot_rejects_invalid_snapshot_type(self): - """An unknown snapshot type raises ValueError before sending.""" + """An unknown or non-enum snapshot type raises TypeError before sending.""" client = IggyClient("127.0.0.1:1") - with pytest.raises(ValueError): - client.snapshot("deflated", ["not-a-real-type"]) + with pytest.raises(TypeError): + client.snapshot(SnapshotCompression.Deflated, ["test"]) @pytest.mark.unit def test_snapshot_rejects_empty_snapshot_types(self): """An empty snapshot_types list raises ValueError before sending.""" client = IggyClient("127.0.0.1:1") with pytest.raises(ValueError): - client.snapshot("deflated", []) + client.snapshot(SnapshotCompression.Deflated, []) @pytest.mark.asyncio async def test_snapshot_rejects_all_combined_with_other_type( @@ -65,13 +69,25 @@ async def test_snapshot_rejects_all_combined_with_other_type( ): """Combining 'all' with another snapshot type is rejected.""" with pytest.raises(RuntimeError, match="(?i)invalid command"): - await iggy_client.snapshot("deflated", ["all", "test"]) + await iggy_client.snapshot( + SnapshotCompression.Deflated, + [SystemSnapshotType.All, SystemSnapshotType.Test], + ) @pytest.mark.asyncio async def test_snapshot_deduplicates_snapshot_types(self, iggy_client: IggyClient): """Duplicate snapshot types are deduplicated before encoding.""" - data = await iggy_client.snapshot("deflated", ["test"] * 255 + [" TEST "]) + data = await iggy_client.snapshot( + SnapshotCompression.Deflated, [SystemSnapshotType.Test] * 255 + ) assert isinstance(data, bytes) assert len(data) > 0 assert zipfile.is_zipfile(io.BytesIO(data)) + + @pytest.mark.unit + def test_snapshot_enums(self): + """Snapshot compression and type enums expose expected variants.""" + assert SnapshotCompression.Deflated == SnapshotCompression.Deflated + assert SnapshotCompression.Deflated != SnapshotCompression.Stored + assert SystemSnapshotType.All == SystemSnapshotType.All From 04c71b65620f3f89000ad7211de368a1d34385c6 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Mon, 26 Oct 2026 20:06:56 +0530 Subject: [PATCH 8/9] test(python): ignore pyrefly bad-argument-type in snapshot negative tests --- foreign/python/tests/test_snapshot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index 483e35d3f8..b2ab3312e9 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -47,6 +47,7 @@ def test_snapshot_rejects_invalid_snapshot_compression(self, invalid_compression """Passing non-enum snapshot_compression raises TypeError before sending.""" client = IggyClient("127.0.0.1:1") with pytest.raises(TypeError): + # pyrefly: ignore # bad-argument-type client.snapshot(invalid_compression, [SystemSnapshotType.Test]) @pytest.mark.unit @@ -54,6 +55,7 @@ def test_snapshot_rejects_invalid_snapshot_type(self): """An unknown or non-enum snapshot type raises TypeError before sending.""" client = IggyClient("127.0.0.1:1") with pytest.raises(TypeError): + # pyrefly: ignore # bad-argument-type client.snapshot(SnapshotCompression.Deflated, ["test"]) @pytest.mark.unit From d63fccd15cfc2ab7dda19a41203d3eb36b5eeb09 Mon Sep 17 00:00:00 2001 From: Debangshu Das Date: Thu, 29 Oct 2026 20:04:40 +0530 Subject: [PATCH 9/9] refactor(python): address maintainer feedback --- foreign/python/src/client.rs | 4 +- foreign/python/src/snapshot.rs | 58 --------------------------- foreign/python/tests/test_snapshot.py | 7 ---- 3 files changed, 2 insertions(+), 67 deletions(-) diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs index 340610ddaf..c7e90cacf1 100644 --- a/foreign/python/src/client.rs +++ b/foreign/python/src/client.rs @@ -354,14 +354,14 @@ impl IggyClient { let mut parsed_types = Vec::with_capacity(snapshot_types.len()); for snapshot_type in snapshot_types { - let rust_type: SystemSnapshotType = snapshot_type.into(); + let rust_type = SystemSnapshotType::from(snapshot_type); if !parsed_types.contains(&rust_type) { parsed_types.push(rust_type); } } let inner = self.inner.clone(); - let compression: SnapshotCompression = snapshot_compression.into(); + let compression = SnapshotCompression::from(snapshot_compression); future_into_py(py, async move { let snapshot = inner .snapshot(compression, parsed_types) diff --git a/foreign/python/src/snapshot.rs b/foreign/python/src/snapshot.rs index 8e5f9658a9..725fc4f968 100644 --- a/foreign/python/src/snapshot.rs +++ b/foreign/python/src/snapshot.rs @@ -114,61 +114,3 @@ impl From for SystemSnapshotType { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn snapshot_compression_converts_bidirectionally() { - let compressions = [ - (SnapshotCompression::Stored, RustSnapshotCompression::Stored), - ( - SnapshotCompression::Deflated, - RustSnapshotCompression::Deflated, - ), - (SnapshotCompression::Bzip2, RustSnapshotCompression::Bzip2), - (SnapshotCompression::Zstd, RustSnapshotCompression::Zstd), - (SnapshotCompression::Lzma, RustSnapshotCompression::Lzma), - (SnapshotCompression::Xz, RustSnapshotCompression::Xz), - ]; - - for (py_comp, rust_comp) in compressions { - assert_eq!(RustSnapshotCompression::from(py_comp), rust_comp); - assert_eq!(SnapshotCompression::from(rust_comp), py_comp); - } - } - - #[test] - fn system_snapshot_type_converts_bidirectionally() { - let types = [ - ( - SystemSnapshotType::FilesystemOverview, - RustSystemSnapshotType::FilesystemOverview, - ), - ( - SystemSnapshotType::ProcessList, - RustSystemSnapshotType::ProcessList, - ), - ( - SystemSnapshotType::ResourceUsage, - RustSystemSnapshotType::ResourceUsage, - ), - (SystemSnapshotType::Test, RustSystemSnapshotType::Test), - ( - SystemSnapshotType::ServerLogs, - RustSystemSnapshotType::ServerLogs, - ), - ( - SystemSnapshotType::ServerConfig, - RustSystemSnapshotType::ServerConfig, - ), - (SystemSnapshotType::All, RustSystemSnapshotType::All), - ]; - - for (py_type, rust_type) in types { - assert_eq!(RustSystemSnapshotType::from(py_type), rust_type); - assert_eq!(SystemSnapshotType::from(rust_type), py_type); - } - } -} diff --git a/foreign/python/tests/test_snapshot.py b/foreign/python/tests/test_snapshot.py index b2ab3312e9..669fac4d79 100644 --- a/foreign/python/tests/test_snapshot.py +++ b/foreign/python/tests/test_snapshot.py @@ -86,10 +86,3 @@ async def test_snapshot_deduplicates_snapshot_types(self, iggy_client: IggyClien assert isinstance(data, bytes) assert len(data) > 0 assert zipfile.is_zipfile(io.BytesIO(data)) - - @pytest.mark.unit - def test_snapshot_enums(self): - """Snapshot compression and type enums expose expected variants.""" - assert SnapshotCompression.Deflated == SnapshotCompression.Deflated - assert SnapshotCompression.Deflated != SnapshotCompression.Stored - assert SystemSnapshotType.All == SystemSnapshotType.All