From 031f280f58c2e2f04171587a1a6519e1febb766c Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Thu, 13 Nov 2025 15:52:31 -0800 Subject: [PATCH 01/13] use get_flows --- .../_internal/low_level_wrappers/ingestion.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py index 0bf38cc37..02ffec851 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py @@ -195,7 +195,7 @@ async def create_sift_stream_instance( sift_stream_instance = await builder.build() known_flows = { - flow.name: FlowConfig._from_rust_config(flow) for flow in ingestion_config.flows + flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in sift_stream_instance.get_flows() } return cls(sift_stream_instance, known_flows) @@ -208,12 +208,9 @@ async def send_requests(self, requests: list[IngestWithConfigDataStreamRequestPy async def add_new_flows(self, flow_configs: list[FlowConfigPy]): await self._sift_stream_instance.add_new_flows(flow_configs) - self._known_flows.update( - { - flow_config.name: FlowConfig._from_rust_config(flow_config) - for flow_config in flow_configs - } - ) + self._known_flows = { + flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in self._sift_stream_instance.get_flows() + } async def attach_run(self, run_selector: RunSelectorPy): await self._sift_stream_instance.attach_run(run_selector) From cbf55abafc294c49b8531a2f0413fa2780a267aa Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Thu, 13 Nov 2025 15:52:43 -0800 Subject: [PATCH 02/13] batch send --- .../_internal/low_level_wrappers/ingestion.py | 5 ++++- python/lib/sift_client/resources/ingestion.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py index 02ffec851..baed467c4 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py @@ -11,7 +11,7 @@ import hashlib import logging from collections import namedtuple -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Iterable, cast from sift.ingestion_configs.v2.ingestion_configs_pb2 import ( GetIngestionConfigRequest, @@ -203,6 +203,9 @@ async def create_sift_stream_instance( async def send(self, flow: FlowPy): await self._sift_stream_instance.send(flow) + async def batch_send(self, flows: Iterable[FlowPy]): + await self._sift_stream_instance.batch_send(flows) + async def send_requests(self, requests: list[IngestWithConfigDataStreamRequestPy]): await self._sift_stream_instance.send_requests(requests) diff --git a/python/lib/sift_client/resources/ingestion.py b/python/lib/sift_client/resources/ingestion.py index 59668378f..3fef1ea49 100644 --- a/python/lib/sift_client/resources/ingestion.py +++ b/python/lib/sift_client/resources/ingestion.py @@ -1,7 +1,8 @@ from __future__ import annotations +from collections.abc import Iterable import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator from sift_client._internal.low_level_wrappers.ingestion import ( IngestionConfigStreamingLowLevelClient, @@ -440,6 +441,17 @@ async def send(self, flow: Flow | FlowPy): flow_py = flow await self._low_level_client.send(flow_py) + async def batch_send(self, flows: Iterable[Flow | FlowPy]): + def normalize_flows(flows: Iterable[Flow | FlowPy]) -> Iterator[FlowPy]: + for flow in flows: + if isinstance(flow, Flow): + yield flow._to_rust_form() + else: + yield flow + + flows_py = normalize_flows(flows) + await self._low_level_client.batch_send(flows_py) + async def send_requests(self, requests: list[IngestWithConfigDataStreamRequestPy]): """Send data in a manner identical to the raw gRPC service for ingestion-config based streaming. From 6d9ef804434af56a7ade62997501d1f25ddd8e49 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Thu, 13 Nov 2025 17:57:00 -0800 Subject: [PATCH 03/13] fix get_flows --- .../lib/sift_client/_internal/low_level_wrappers/ingestion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py index baed467c4..af2fcd409 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py @@ -195,7 +195,7 @@ async def create_sift_stream_instance( sift_stream_instance = await builder.build() known_flows = { - flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in sift_stream_instance.get_flows() + flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in sift_stream_instance.get_flows().items() } return cls(sift_stream_instance, known_flows) @@ -212,7 +212,7 @@ async def send_requests(self, requests: list[IngestWithConfigDataStreamRequestPy async def add_new_flows(self, flow_configs: list[FlowConfigPy]): await self._sift_stream_instance.add_new_flows(flow_configs) self._known_flows = { - flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in self._sift_stream_instance.get_flows() + flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in self._sift_stream_instance.get_flows().items() } async def attach_run(self, run_selector: RunSelectorPy): From 898a623df27b80ba3f3302251c35824c29a485c5 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Thu, 13 Nov 2025 18:42:28 -0800 Subject: [PATCH 04/13] update examples --- python/docs/examples/ingestion.ipynb | 285 ++++++++++++++++++++++++--- 1 file changed, 262 insertions(+), 23 deletions(-) diff --git a/python/docs/examples/ingestion.ipynb b/python/docs/examples/ingestion.ipynb index adf011421..b770689b4 100644 --- a/python/docs/examples/ingestion.ipynb +++ b/python/docs/examples/ingestion.ipynb @@ -5,30 +5,49 @@ "id": "0b202351", "metadata": {}, "source": [ - "# Sift Client Ingestion Basic Example\n", + "# Sift Client Ingestion Examples\n", "\n", - "This notebook demonstrates some examples features of SiftClient ingestion\n", - "- Initializing the Sift client\n", - "- Creating an ingestion config\n", - "- Creating a run\n", - "- Creating and sending flows\n" + "This notebook demonstrates features of SiftClient ingestion, from basic usage to advanced patterns.\n", + "\n", + "## Prerequisites\n", + "\n", + "**Important**: The ingestion streaming client requires the `sift-stream` optional dependency. Install it with:\n", + "\n", + "```bash\n", + "pip install sift-stack-py[sift-stream]\n", + "```\n", + "\n", + "## Topics Covered\n", + "\n", + "1. **Basic Example**: Simple single flow sending\n", + "2. **Batch Sending**: Efficiently sending multiple flows at once\n", + "3. **Advanced Concepts**: Recovery strategies, checkpoints, and more\n" + ] + }, + { + "cell_type": "markdown", + "id": "b324ed85", + "metadata": {}, + "source": [ + "## 1. Basic Example: Sending Individual Flows\n", + "\n", + "This example shows the simplest way to send telemetry data to Sift:\n", + "- Create an ingestion config with flow definitions\n", + "- Create a run to associate data with\n", + "- Send individual flows one at a time" ] }, { "cell_type": "code", "execution_count": null, - "id": "02268d76", - "metadata": { - "vscode": { - "languageId": "plaintext" - } - }, + "id": "6b75dbce", + "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "import random\n", "import time\n", - "from datetime import datetime, timezone\n", + "from datetime import datetime, timedelta, timezone\n", "\n", "from sift_client import SiftClient, SiftConnectionConfig\n", "from sift_client.sift_types import (\n", @@ -40,7 +59,8 @@ ")\n", "\n", "\n", - "async def main():\n", + "async def basic_example():\n", + " # Configure connection to Sift\n", " connection_config = SiftConnectionConfig(\n", " api_key=\"my_api_key\",\n", " grpc_url=\"sift_grpc_url\",\n", @@ -49,31 +69,241 @@ "\n", " client = SiftClient(connection_config=connection_config)\n", "\n", - " # Ingestion configs are created using SiftClient types\n", + " # Define your telemetry schema using an ingestion config\n", " ingestion_config = IngestionConfigCreate(\n", " asset_name=\"sift_rover_1\",\n", " flows=[\n", " FlowConfig(\n", " name=\"onboard_sensors\",\n", " channels=[\n", - " ChannelConfig(name=\"motor_temp\", unit=\"C\", data_type=ChannelDataType.DOUBLE),\n", " ChannelConfig(\n", - " name=\"tank_pressure\", unit=\"kPa\", data_type=ChannelDataType.DOUBLE\n", + " name=\"motor_temp\",\n", + " unit=\"C\",\n", + " data_type=ChannelDataType.DOUBLE\n", + " ),\n", + " ChannelConfig(\n", + " name=\"tank_pressure\",\n", + " unit=\"kPa\",\n", + " data_type=ChannelDataType.DOUBLE\n", " ),\n", " ],\n", " )\n", " ],\n", " )\n", "\n", + " # Create a run to associate this data collection session\n", " run = RunCreate(name=\"sift_rover-\" + str(int(time.time())))\n", "\n", + " # Create the streaming client\n", " async with await client.async_.ingestion.create_ingestion_config_streaming_client(\n", " ingestion_config=ingestion_config,\n", " run=run,\n", " ) as ingest_client:\n", - " while True:\n", - " # Flows can be generated easily from the ingest client\n", + " # Send data in a loop\n", + " for i in range(10):\n", + " # Get the flow config to create flows\n", " flow_config = ingest_client.get_flow_config(flow_name=\"onboard_sensors\")\n", + "\n", + " # Create a flow with timestamp and values\n", + " # The timestamp can also be left out to default to datetime.now(timezone.utc)\n", + " flow = flow_config.as_flow(\n", + " timestamp=datetime.now(timezone.utc),\n", + " values={\n", + " \"motor_temp\": 50.0 + random.random() * 5.0,\n", + " \"tank_pressure\": 2000.0 + random.random() * 100.0,\n", + " },\n", + " )\n", + "\n", + " # Send the flow to Sift\n", + " await ingest_client.send(flow=flow)\n", + "\n", + " await asyncio.sleep(0.1)\n", + "\n", + "\n", + "# Uncomment to run:\n", + "# asyncio.run(basic_example())\n" + ] + }, + { + "cell_type": "markdown", + "id": "c5bf10f3", + "metadata": {}, + "source": [ + "## 2. Batch Sending: Efficiently Sending Multiple Flows\n", + "\n", + "For potentially better performance when sending many flows, use `batch_send()` to send multiple flows in a single operation. This may reduce the overhead of calling the underlying Rust SiftStream ingestion client.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98aec10b", + "metadata": {}, + "outputs": [], + "source": [ + "async def batch_send_example():\n", + " \"\"\"Example showing how to efficiently send multiple flows at once.\"\"\"\n", + " connection_config = SiftConnectionConfig(\n", + " api_key=\"my_api_key\",\n", + " grpc_url=\"sift_grpc_url\",\n", + " rest_url=\"sift_rest_url\",\n", + " )\n", + "\n", + " client = SiftClient(connection_config=connection_config)\n", + "\n", + " ingestion_config = IngestionConfigCreate(\n", + " asset_name=\"sift_rover_1\",\n", + " flows=[\n", + " FlowConfig(\n", + " name=\"onboard_sensors\",\n", + " channels=[\n", + " ChannelConfig(\n", + " name=\"motor_temp\",\n", + " unit=\"C\",\n", + " data_type=ChannelDataType.DOUBLE\n", + " ),\n", + " ChannelConfig(\n", + " name=\"tank_pressure\",\n", + " unit=\"kPa\",\n", + " data_type=ChannelDataType.DOUBLE\n", + " ),\n", + " ],\n", + " )\n", + " ],\n", + " )\n", + "\n", + " run = RunCreate(name=\"sift_rover-\" + str(int(time.time())))\n", + "\n", + " async with await client.async_.ingestion.create_ingestion_config_streaming_client(\n", + " ingestion_config=ingestion_config,\n", + " run=run,\n", + " ) as ingest_client:\n", + " flow_config = ingest_client.get_flow_config(flow_name=\"onboard_sensors\")\n", + "\n", + " # Generate 5 seconds of data at 10Hz (10 flows per second = 50 flows total)\n", + " sample_rate_hz = 10\n", + " duration_seconds = 5\n", + " num_flows = sample_rate_hz * duration_seconds # 50 flows\n", + "\n", + " start_time = datetime.now(timezone.utc)\n", + " flows = []\n", + " for i in range(num_flows):\n", + " # Calculate timestamp for each sample (spaced 0.1 seconds apart)\n", + " timestamp = start_time + timedelta(seconds=i / sample_rate_hz)\n", + " flows.append(\n", + " flow_config.as_flow(\n", + " timestamp=timestamp,\n", + " values={\n", + " \"motor_temp\": 50.0 + random.random() * 5.0,\n", + " \"tank_pressure\": 2000.0 + random.random() * 100.0,\n", + " },\n", + " )\n", + " )\n", + "\n", + " # Send all flows in a single batch operation\n", + " # batch_send supports sending any iterables of Flow or FlowPy objects\n", + " await ingest_client.batch_send(flows)\n", + "\n", + "\n", + "# Uncomment to run:\n", + "# asyncio.run(batch_send_example())\n" + ] + }, + { + "cell_type": "markdown", + "id": "ab425ee7", + "metadata": {}, + "source": [ + "## 3. Advanced Concepts\n", + "\n", + "### Recovery Strategies\n", + "\n", + "Recovery strategies can be used to allow fine-tuned control of SiftStream ingestion:\n", + "- **Retry with Backups [DEFAULT]**: Retry failed connections + temporarily keep backups of ingested data for automatic re-ingestion if a streaming checkpoint (defaults to 60s) fails to send all data.\n", + "- **Retry Only**: Retry failed connections only. More performant, but with no guarantee of data ingestion in the event of a connection issue.\n", + "\n", + "### Tracing\n", + "\n", + "Tracing allows you to monitor and debug SiftStream ingestion through logs. You can configure tracing in several ways:\n", + "\n", + "- **Console Only**: Output logs to stdout/stderr only\n", + "- **File Logging**: Output logs to both console and rolling log files\n", + "- **Disabled**: Turn off tracing entirely\n", + "\n", + "Tracing is initialized once per process, and cannot be modified afterward.\n", + "\n", + "### Metrics\n", + "\n", + "SiftStream provides detailed metrics about ingestion performance. Use `get_metrics_snapshot()` to access:\n", + "\n", + "- **Bytes sent**: Total bytes successfully sent to Sift\n", + "- **Byte rate**: Current throughput in bytes per second\n", + "- **Messages sent**: Total number of flows/messages sent\n", + "- **Message rate**: Current message throughput\n", + "- **Checkpoint metrics**: Timing and counts for checkpoints\n", + "- **Backup metrics**: Statistics about disk backups (if enabled)\n", + "\n", + "Metrics are updated in real-time and can help you monitor ingestion health and performance.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc45400a", + "metadata": {}, + "outputs": [], + "source": [ + "from sift_client.resources.ingestion import RecoveryStrategyConfig, TracingConfig\n", + "\n", + "async def advanced_example():\n", + " \"\"\"Example with recovery strategies, tracing, and metrics.\"\"\"\n", + " connection_config = SiftConnectionConfig(\n", + " api_key=\"my_api_key\",\n", + " grpc_url=\"sift_grpc_url\",\n", + " rest_url=\"sift_rest_url\",\n", + " )\n", + "\n", + " client = SiftClient(connection_config=connection_config)\n", + "\n", + " ingestion_config = IngestionConfigCreate(\n", + " asset_name=\"sift_rover_1\",\n", + " flows=[\n", + " FlowConfig(\n", + " name=\"onboard_sensors\",\n", + " channels=[\n", + " ChannelConfig(\n", + " name=\"motor_temp\",\n", + " unit=\"C\",\n", + " data_type=ChannelDataType.DOUBLE\n", + " ),\n", + " ChannelConfig(\n", + " name=\"tank_pressure\",\n", + " unit=\"kPa\",\n", + " data_type=ChannelDataType.DOUBLE\n", + " ),\n", + " ],\n", + " )\n", + " ],\n", + " )\n", + "\n", + " run = RunCreate(name=\"sift_rover-\" + str(int(time.time())))\n", + "\n", + " # Use retry only for better performance (no backups)\n", + " recovery_strategy = RecoveryStrategyConfig.retry_only()\n", + "\n", + " # Use console-only tracing (stdout/stderr) instead of file logging\n", + " tracing_config = TracingConfig.console_only(level=\"info\")\n", + "\n", + " async with await client.async_.ingestion.create_ingestion_config_streaming_client(\n", + " ingestion_config=ingestion_config,\n", + " run=run,\n", + " recovery_strategy=recovery_strategy,\n", + " tracing_config=tracing_config,\n", + " ) as ingest_client:\n", + " flow_config = ingest_client.get_flow_config(flow_name=\"onboard_sensors\")\n", + "\n", + " # Send some flows\n", + " for i in range(10):\n", " flow = flow_config.as_flow(\n", " timestamp=datetime.now(timezone.utc),\n", " values={\n", @@ -81,14 +311,23 @@ " \"tank_pressure\": 2000.0 + random.random() * 100.0,\n", " },\n", " )\n", - " # Ingest the flow with .send()\n", " await ingest_client.send(flow=flow)\n", "\n", - " await asyncio.sleep(1)\n", + " # Get metrics snapshot to see ingestion statistics\n", + " metrics = ingest_client.get_metrics_snapshot()\n", + " print(\"\\n=== Ingestion Metrics ===\")\n", + " print(f\"Bytes sent: {metrics.bytes_sent:,}\")\n", + " print(f\"Byte rate: {metrics.byte_rate:,} bytes/s\")\n", + " print(f\"Messages sent: {metrics.messages_sent:,}\")\n", + " print(f\"Message rate: {metrics.message_rate:.2f} messages/s\")\n", + "\n", + " # Additional metrics available:\n", + " # - metrics.checkpoint_metrics: Checkpoint timing and counts\n", + " # - metrics.backup_metrics: Backup statistics (if backups enabled)\n", "\n", "\n", - "if __name__ == \"__main__\":\n", - " asyncio.run(main())\n" + "# Uncomment to run:\n", + "# asyncio.run(advanced_example())\n" ] } ], From 10f28e08d18cc7745f31701c8584719b9c914d95 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Thu, 13 Nov 2025 18:49:53 -0800 Subject: [PATCH 05/13] Add better import errors --- python/lib/sift_client/resources/ingestion.py | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/python/lib/sift_client/resources/ingestion.py b/python/lib/sift_client/resources/ingestion.py index 3fef1ea49..c70e336fa 100644 --- a/python/lib/sift_client/resources/ingestion.py +++ b/python/lib/sift_client/resources/ingestion.py @@ -146,7 +146,13 @@ def __init__(self, recovery_strategy_py: RecoveryStrategyPy | None): instead of calling this constructor directly. """ # Importing here to allow sift_stream_bindings to be an optional dependancy for non-ingestion users - from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy + try: + from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy + except ImportError as e: + raise ImportError( + "sift_stream_bindings is required for ingestion streaming functionality. " + "Install it with: pip install sift-stack-py[sift-stream]" + ) from e # Default to retry_with_backups() # This is intentionally different from SiftStream, which defaults to retry_only @@ -175,7 +181,13 @@ def retry_only(cls, retry_policy: RetryPolicyPy | None = None) -> RecoveryStrate A RecoveryStrategyConfig configured for retry-only strategy. """ # Importing here to allow sift_stream_bindings to be an optional dependancy for non-ingestion users - from sift_stream_bindings import RecoveryStrategyPy, RetryPolicyPy + try: + from sift_stream_bindings import RecoveryStrategyPy, RetryPolicyPy + except ImportError as e: + raise ImportError( + "sift_stream_bindings is required for ingestion streaming functionality. " + "Install it with: pip install sift-stack-py[sift-stream]" + ) from e retry_policy_py = retry_policy or RetryPolicyPy.default() @@ -201,7 +213,13 @@ def retry_with_backups( A RecoveryStrategyConfig configured for retry with disk backups. """ # Importing here to allow sift_stream_bindings to be an optional dependancy for non-ingestion users - from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy + try: + from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy + except ImportError as e: + raise ImportError( + "sift_stream_bindings is required for ingestion streaming functionality. " + "Install it with: pip install sift-stack-py[sift-stream]" + ) from e retry_policy_py = retry_policy or RetryPolicyPy.default() disk_backup_policy_py = disk_backup_policy or DiskBackupPolicyPy.default() @@ -326,14 +344,20 @@ async def _create( An initialized IngestionConfigStreamingClient. """ # Importing here to allow sift_stream_bindings to be an optional dependancy for non-ingestion users - from sift_stream_bindings import ( - DurationPy, - IngestionConfigFormPy, - MetadataPy, - MetadataValuePy, - RecoveryStrategyPy, - RunFormPy, - ) + try: + from sift_stream_bindings import ( + DurationPy, + IngestionConfigFormPy, + MetadataPy, + MetadataValuePy, + RecoveryStrategyPy, + RunFormPy, + ) + except ImportError as e: + raise ImportError( + "sift_stream_bindings is required for ingestion streaming functionality. " + "Install it with: pip install sift-stack-py[sift-stream]" + ) from e instance = cls.__new__(cls) instance._sift_client = sift_client From 413b8e1ea023da4173d68596a7abbbf739771d50 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 10:07:08 -0800 Subject: [PATCH 06/13] bump sift-stream-bindings version --- python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 4dbc5664b..d3c4012b7 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -213,7 +213,7 @@ docs = ["mkdocs", openssl = ["pyOpenSSL<24.0.0", "types-pyOpenSSL<24.0.0", "cffi~=1.14"] tdms = ["npTDMS~=1.9"] rosbags = ["rosbags~=0.0"] -sift-stream = ["sift-stream-bindings>=0.2.0-rc"] +sift-stream = ["sift-stream-bindings>=0.2.0-rc2"] hdf5 = ["h5py~=3.11", "polars~=1.8"] data-review = ["pyarrow>=17.0.0"] From e72bc2703b885a096678ab06a7fefd27fe2393b4 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:07:37 -0800 Subject: [PATCH 07/13] doc title updates --- python/mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mkdocs.yml b/python/mkdocs.yml index ed73da187..94c508740 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -56,8 +56,8 @@ nav: - Sift Py API - Sift Client API (New) - Examples: - - examples/basic.ipynb - - examples/ingestion.ipynb + - Sift Client Usage: examples/basic.ipynb + - Sift Client Ingestion: examples/ingestion.ipynb # - Guides: # - Logging # - Error Handling From 3553f1bf3e3fc4517d0508dfb153900cacb3c939 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:11:53 -0800 Subject: [PATCH 08/13] Moved import error to errors.py --- python/lib/sift_client/errors.py | 7 +++++++ python/lib/sift_client/resources/ingestion.py | 21 +++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/python/lib/sift_client/errors.py b/python/lib/sift_client/errors.py index 471d3898f..f027b5e9f 100644 --- a/python/lib/sift_client/errors.py +++ b/python/lib/sift_client/errors.py @@ -24,3 +24,10 @@ def _sift_client_experimental_warning(): stacklevel=2, ) _sift_client_experimental_warned = True + + +def _sift_stream_bindings_import_error(original_error: ImportError) -> ImportError: + raise ImportError( + "sift_stream_bindings is required for ingestion streaming functionality. " + "Install it with: pip install sift-stack-py[sift-stream]" + ) from original_error diff --git a/python/lib/sift_client/resources/ingestion.py b/python/lib/sift_client/resources/ingestion.py index c70e336fa..4d18763e6 100644 --- a/python/lib/sift_client/resources/ingestion.py +++ b/python/lib/sift_client/resources/ingestion.py @@ -8,6 +8,7 @@ IngestionConfigStreamingLowLevelClient, IngestionLowLevelClient, ) +from sift_client.errors import _sift_stream_bindings_import_error from sift_client.resources._base import ResourceBase from sift_client.sift_types.ingestion import Flow, IngestionConfig, IngestionConfigCreate from sift_client.sift_types.run import Run, RunCreate, Tag @@ -149,10 +150,7 @@ def __init__(self, recovery_strategy_py: RecoveryStrategyPy | None): try: from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy except ImportError as e: - raise ImportError( - "sift_stream_bindings is required for ingestion streaming functionality. " - "Install it with: pip install sift-stack-py[sift-stream]" - ) from e + _sift_stream_bindings_import_error(e) # Default to retry_with_backups() # This is intentionally different from SiftStream, which defaults to retry_only @@ -184,10 +182,7 @@ def retry_only(cls, retry_policy: RetryPolicyPy | None = None) -> RecoveryStrate try: from sift_stream_bindings import RecoveryStrategyPy, RetryPolicyPy except ImportError as e: - raise ImportError( - "sift_stream_bindings is required for ingestion streaming functionality. " - "Install it with: pip install sift-stack-py[sift-stream]" - ) from e + _sift_stream_bindings_import_error(e) retry_policy_py = retry_policy or RetryPolicyPy.default() @@ -216,10 +211,7 @@ def retry_with_backups( try: from sift_stream_bindings import DiskBackupPolicyPy, RecoveryStrategyPy, RetryPolicyPy except ImportError as e: - raise ImportError( - "sift_stream_bindings is required for ingestion streaming functionality. " - "Install it with: pip install sift-stack-py[sift-stream]" - ) from e + _sift_stream_bindings_import_error(e) retry_policy_py = retry_policy or RetryPolicyPy.default() disk_backup_policy_py = disk_backup_policy or DiskBackupPolicyPy.default() @@ -354,10 +346,7 @@ async def _create( RunFormPy, ) except ImportError as e: - raise ImportError( - "sift_stream_bindings is required for ingestion streaming functionality. " - "Install it with: pip install sift-stack-py[sift-stream]" - ) from e + _sift_stream_bindings_import_error(e) instance = cls.__new__(cls) instance._sift_client = sift_client From cffaad432256676a5968089971ff7d3a07e79496 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:16:49 -0800 Subject: [PATCH 09/13] ruff and docs --- python/docs/examples/ingestion.ipynb | 1 + python/lib/sift_client/resources/ingestion.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/python/docs/examples/ingestion.ipynb b/python/docs/examples/ingestion.ipynb index b770689b4..f0b4f35d4 100644 --- a/python/docs/examples/ingestion.ipynb +++ b/python/docs/examples/ingestion.ipynb @@ -255,6 +255,7 @@ "source": [ "from sift_client.resources.ingestion import RecoveryStrategyConfig, TracingConfig\n", "\n", + "\n", "async def advanced_example():\n", " \"\"\"Example with recovery strategies, tracing, and metrics.\"\"\"\n", " connection_config = SiftConnectionConfig(\n", diff --git a/python/lib/sift_client/resources/ingestion.py b/python/lib/sift_client/resources/ingestion.py index 4d18763e6..70b296f35 100644 --- a/python/lib/sift_client/resources/ingestion.py +++ b/python/lib/sift_client/resources/ingestion.py @@ -1,6 +1,5 @@ from __future__ import annotations -from collections.abc import Iterable import logging from typing import TYPE_CHECKING, Iterator @@ -14,6 +13,8 @@ from sift_client.sift_types.run import Run, RunCreate, Tag if TYPE_CHECKING: + from collections.abc import Iterable + from sift_stream_bindings import ( DiskBackupPolicyPy, DurationPy, @@ -455,6 +456,15 @@ async def send(self, flow: Flow | FlowPy): await self._low_level_client.send(flow_py) async def batch_send(self, flows: Iterable[Flow | FlowPy]): + """Send multiple flows to Sift in a single batch operation. + + This method allows you to send multiple flows efficiently in a single batch, + which can improve performance by reducing overhead compared to calling `send` + multiple times. + + Args: + flows: An iterable of flows to send. Each flow can be either a `Flow` or `FlowPy` instance. + """ def normalize_flows(flows: Iterable[Flow | FlowPy]) -> Iterator[FlowPy]: for flow in flows: if isinstance(flow, Flow): From 473cd1b1928d2de4256e65f2d231be00774325e8 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:19:01 -0800 Subject: [PATCH 10/13] ruff --- python/docs/examples/ingestion.ipynb | 36 +++++-------------- .../_internal/low_level_wrappers/ingestion.py | 6 ++-- python/lib/sift_client/resources/ingestion.py | 1 + 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/python/docs/examples/ingestion.ipynb b/python/docs/examples/ingestion.ipynb index f0b4f35d4..505951caa 100644 --- a/python/docs/examples/ingestion.ipynb +++ b/python/docs/examples/ingestion.ipynb @@ -76,15 +76,9 @@ " FlowConfig(\n", " name=\"onboard_sensors\",\n", " channels=[\n", + " ChannelConfig(name=\"motor_temp\", unit=\"C\", data_type=ChannelDataType.DOUBLE),\n", " ChannelConfig(\n", - " name=\"motor_temp\",\n", - " unit=\"C\",\n", - " data_type=ChannelDataType.DOUBLE\n", - " ),\n", - " ChannelConfig(\n", - " name=\"tank_pressure\",\n", - " unit=\"kPa\",\n", - " data_type=ChannelDataType.DOUBLE\n", + " name=\"tank_pressure\", unit=\"kPa\", data_type=ChannelDataType.DOUBLE\n", " ),\n", " ],\n", " )\n", @@ -121,7 +115,7 @@ "\n", "\n", "# Uncomment to run:\n", - "# asyncio.run(basic_example())\n" + "# asyncio.run(basic_example())" ] }, { @@ -157,15 +151,9 @@ " FlowConfig(\n", " name=\"onboard_sensors\",\n", " channels=[\n", + " ChannelConfig(name=\"motor_temp\", unit=\"C\", data_type=ChannelDataType.DOUBLE),\n", " ChannelConfig(\n", - " name=\"motor_temp\",\n", - " unit=\"C\",\n", - " data_type=ChannelDataType.DOUBLE\n", - " ),\n", - " ChannelConfig(\n", - " name=\"tank_pressure\",\n", - " unit=\"kPa\",\n", - " data_type=ChannelDataType.DOUBLE\n", + " name=\"tank_pressure\", unit=\"kPa\", data_type=ChannelDataType.DOUBLE\n", " ),\n", " ],\n", " )\n", @@ -206,7 +194,7 @@ "\n", "\n", "# Uncomment to run:\n", - "# asyncio.run(batch_send_example())\n" + "# asyncio.run(batch_send_example())" ] }, { @@ -272,15 +260,9 @@ " FlowConfig(\n", " name=\"onboard_sensors\",\n", " channels=[\n", + " ChannelConfig(name=\"motor_temp\", unit=\"C\", data_type=ChannelDataType.DOUBLE),\n", " ChannelConfig(\n", - " name=\"motor_temp\",\n", - " unit=\"C\",\n", - " data_type=ChannelDataType.DOUBLE\n", - " ),\n", - " ChannelConfig(\n", - " name=\"tank_pressure\",\n", - " unit=\"kPa\",\n", - " data_type=ChannelDataType.DOUBLE\n", + " name=\"tank_pressure\", unit=\"kPa\", data_type=ChannelDataType.DOUBLE\n", " ),\n", " ],\n", " )\n", @@ -328,7 +310,7 @@ "\n", "\n", "# Uncomment to run:\n", - "# asyncio.run(advanced_example())\n" + "# asyncio.run(advanced_example())" ] } ], diff --git a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py index af2fcd409..814ec115e 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/ingestion.py @@ -195,7 +195,8 @@ async def create_sift_stream_instance( sift_stream_instance = await builder.build() known_flows = { - flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in sift_stream_instance.get_flows().items() + flow_name: FlowConfig._from_rust_config(flow) + for flow_name, flow in sift_stream_instance.get_flows().items() } return cls(sift_stream_instance, known_flows) @@ -212,7 +213,8 @@ async def send_requests(self, requests: list[IngestWithConfigDataStreamRequestPy async def add_new_flows(self, flow_configs: list[FlowConfigPy]): await self._sift_stream_instance.add_new_flows(flow_configs) self._known_flows = { - flow_name: FlowConfig._from_rust_config(flow) for flow_name, flow in self._sift_stream_instance.get_flows().items() + flow_name: FlowConfig._from_rust_config(flow) + for flow_name, flow in self._sift_stream_instance.get_flows().items() } async def attach_run(self, run_selector: RunSelectorPy): diff --git a/python/lib/sift_client/resources/ingestion.py b/python/lib/sift_client/resources/ingestion.py index 70b296f35..884706ca1 100644 --- a/python/lib/sift_client/resources/ingestion.py +++ b/python/lib/sift_client/resources/ingestion.py @@ -465,6 +465,7 @@ async def batch_send(self, flows: Iterable[Flow | FlowPy]): Args: flows: An iterable of flows to send. Each flow can be either a `Flow` or `FlowPy` instance. """ + def normalize_flows(flows: Iterable[Flow | FlowPy]) -> Iterator[FlowPy]: for flow in flows: if isinstance(flow, Flow): From 6ca7970356baa1ae47502f5efc3a284538d0a2f5 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:29:07 -0800 Subject: [PATCH 11/13] pyright fix --- python/lib/sift_client/errors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/lib/sift_client/errors.py b/python/lib/sift_client/errors.py index f027b5e9f..4a4176f48 100644 --- a/python/lib/sift_client/errors.py +++ b/python/lib/sift_client/errors.py @@ -1,6 +1,7 @@ from __future__ import annotations import warnings +from typing import NoReturn class SiftWarning(UserWarning): @@ -26,7 +27,8 @@ def _sift_client_experimental_warning(): _sift_client_experimental_warned = True -def _sift_stream_bindings_import_error(original_error: ImportError) -> ImportError: +def _sift_stream_bindings_import_error(original_error: ImportError) -> NoReturn: + # Returns NoReturn to satisfy pyright raise ImportError( "sift_stream_bindings is required for ingestion streaming functionality. " "Install it with: pip install sift-stack-py[sift-stream]" From ae1825bd650cb960e196857157d518de648d2e35 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:38:50 -0800 Subject: [PATCH 12/13] docs --- python/docs/examples/basic.ipynb | 156 ++++----------------------- python/docs/examples/ingestion.ipynb | 7 +- python/mkdocs.yml | 4 +- 3 files changed, 25 insertions(+), 142 deletions(-) diff --git a/python/docs/examples/basic.ipynb b/python/docs/examples/basic.ipynb index d2ebae108..76770661f 100644 --- a/python/docs/examples/basic.ipynb +++ b/python/docs/examples/basic.ipynb @@ -5,7 +5,7 @@ "id": "header", "metadata": {}, "source": [ - "# Sift Client Basic Example\n", + "# Sift Client Usage\n", "\n", "This notebook demonstrates the core features of the Sift Python client:\n", "- Initializing the Sift client\n", @@ -80,16 +80,13 @@ { "data": { "text/html": [ - "
✓ Sift client initialized successfully\n",
+       "
\u2713 Sift client initialized successfully\n",
        "
\n" ], "text/plain": [ - "✓ Sift client initialized successfully\n" + "\u2713 Sift client initialized successfully\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -103,7 +100,7 @@ "\n", "client = SiftClient(api_key=api_key, grpc_url=grpc_url, rest_url=rest_url)\n", "\n", - "print(\"✓ Sift client initialized successfully\")" + "print(\"\u2713 Sift client initialized successfully\")" ] }, { @@ -215,9 +212,6 @@ "Name: MarsRoverIngestPusher, ID: \u001b[93m429b864b-0911-4e23-b9d1-2a1fba4d441c\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -231,9 +225,6 @@ "Name: Mars Rover \u001b[1m[\u001b[0mJonno export test\u001b[1m]\u001b[0m, ID: \u001b[93m5d86ed46-dec6-41c8-8680-a0ba02d9e546\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -247,9 +238,6 @@ "Name: MarsRover_pb4, ID: \u001b[93m9bb3bfec-840d-40b7-a5ab-25591f9a1b38\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -263,9 +251,6 @@ "Name: MarsRover, ID: \u001b[93m611914d3-ffb1-402e-ae1e-5eb3e66dea7c\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -279,9 +264,6 @@ "Name: MarsRover42NaN, ID: \u001b[93m0ad88099-1aea-461c-91b5-0a91c7811f74\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -297,7 +279,9 @@ "cell_type": "markdown", "id": "a04618d6724e3a2d", "metadata": {}, - "source": "`find` can be used to find a single matching object. It will return an error if multiple are found. It takes the same arguments and filters as `list_`." + "source": [ + "`find` can be used to find a single matching object. It will return an error if multiple are found. It takes the same arguments and filters as `list_`." + ] }, { "cell_type": "code", @@ -344,9 +328,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -362,7 +343,9 @@ "cell_type": "markdown", "id": "1df0e447f3a04c98", "metadata": {}, - "source": "When we know exactly what we are looking for, we can use `get`." + "source": [ + "When we know exactly what we are looking for, we can use `get`." + ] }, { "cell_type": "code", @@ -409,9 +392,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -495,9 +475,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -573,9 +550,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -652,9 +626,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -696,9 +667,6 @@ "Found \u001b[1;36m5\u001b[0m channels for asset \u001b[32m'MarsRover0'\u001b[0m:\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -738,9 +706,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -780,9 +745,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -822,9 +784,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -864,9 +823,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -916,9 +872,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -953,9 +906,6 @@ "Channels containing \u001b[32m'velocity'\u001b[0m: \u001b[1;36m1\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -995,9 +945,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1033,9 +980,6 @@ "Channels in run \u001b[32m'Updated Test Run'\u001b[0m: \u001b[1;36m0\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1074,17 +1018,14 @@ "data": { "text/html": [ "
\n",
-       "✓ Retrieved data for 4 channels:\n",
+       "\u2713 Retrieved data for 4 channels:\n",
        "
\n" ], "text/plain": [ "\n", - "✓ Retrieved data for \u001b[1;36m4\u001b[0m channels:\n" + "\u2713 Retrieved data for \u001b[1;36m4\u001b[0m channels:\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1100,9 +1041,6 @@ " Channel: is_even\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1116,9 +1054,6 @@ " Data points: \u001b[1;36m1000\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1142,9 +1077,6 @@ "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m980486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[3;92mTrue\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1160,9 +1092,6 @@ " Channel: mainmotor.velocity\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1176,9 +1105,6 @@ " Data points: \u001b[1;36m1000\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1202,9 +1128,6 @@ "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m980486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m16.148490\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1220,9 +1143,6 @@ " Channel: vehicle_state\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1236,9 +1156,6 @@ " Data points: \u001b[1;36m1000\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1262,9 +1179,6 @@ "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m980486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m1\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1280,9 +1194,6 @@ " Channel: voltage\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1296,9 +1207,6 @@ " Data points: \u001b[1;36m1000\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1322,9 +1230,6 @@ "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m980486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m2\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1342,7 +1247,7 @@ " limit=1000, # Limit to 1000 data points per channel\n", ")\n", "\n", - "print(f\"\\n✓ Retrieved data for {len(data)} channels:\")\n", + "print(f\"\\n\u2713 Retrieved data for {len(data)} channels:\")\n", "for channel_name, df in data.items():\n", " print(f\"\\n Channel: {channel_name}\")\n", " print(f\" Data points: {len(df)}\")\n", @@ -1380,9 +1285,6 @@ "Calculated channel \u001b[32m'is_even_per_voltage'\u001b[0m already exists\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1446,9 +1348,6 @@ "\u001b[1m)\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1513,9 +1412,6 @@ "Calculated channels for asset \u001b[32m'MarsRover0'\u001b[0m: \u001b[1;36m1\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1529,9 +1425,6 @@ " - is_even_per_voltage\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1545,9 +1438,6 @@ " Expression: $\u001b[1;36m1\u001b[0m \u001b[35m/\u001b[0m $\u001b[1;36m2\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1561,9 +1451,6 @@ " Version: \u001b[1;36m1\u001b[0m\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1600,9 +1487,6 @@ "Archived calculated channel: is_even_per_voltage\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" }, @@ -1610,17 +1494,14 @@ "data": { "text/html": [ "
\n",
-       "✓ Example complete!\n",
+       "\u2713 Example complete!\n",
        "
\n" ], "text/plain": [ "\n", - "✓ Example complete!\n" + "\u2713 Example complete!\n" ] }, - "jetTransient": { - "display_id": null - }, "metadata": {}, "output_type": "display_data" } @@ -1633,7 +1514,7 @@ " calc_channel.archive()\n", " print(f\"Archived calculated channel: {calc_channel.name}\")\n", "\n", - "print(\"\\n✓ Example complete!\")" + "print(\"\\n\u2713 Example complete!\")" ] } ], @@ -1654,8 +1535,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.0" - } + }, + "title": "Sift Client Usage" }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/python/docs/examples/ingestion.ipynb b/python/docs/examples/ingestion.ipynb index 505951caa..6339da2dc 100644 --- a/python/docs/examples/ingestion.ipynb +++ b/python/docs/examples/ingestion.ipynb @@ -5,7 +5,7 @@ "id": "0b202351", "metadata": {}, "source": [ - "# Sift Client Ingestion Examples\n", + "# Sift Client Ingestion\n", "\n", "This notebook demonstrates features of SiftClient ingestion, from basic usage to advanced patterns.\n", "\n", @@ -317,8 +317,9 @@ "metadata": { "language_info": { "name": "python" - } + }, + "title": "Sift Client Ingestion" }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 94c508740..ed73da187 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -56,8 +56,8 @@ nav: - Sift Py API - Sift Client API (New) - Examples: - - Sift Client Usage: examples/basic.ipynb - - Sift Client Ingestion: examples/ingestion.ipynb + - examples/basic.ipynb + - examples/ingestion.ipynb # - Guides: # - Logging # - Error Handling From a2a8510a6a6cf3be53f671d9986ad7ab91bd8517 Mon Sep 17 00:00:00 2001 From: Nathan Federknopp Date: Fri, 14 Nov 2025 13:45:27 -0800 Subject: [PATCH 13/13] Update doc title --- python/docs/examples/basic.ipynb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python/docs/examples/basic.ipynb b/python/docs/examples/basic.ipynb index 76770661f..1cc63ddbc 100644 --- a/python/docs/examples/basic.ipynb +++ b/python/docs/examples/basic.ipynb @@ -5,7 +5,7 @@ "id": "header", "metadata": {}, "source": [ - "# Sift Client Usage\n", + "# Sift Client Basic Usage\n", "\n", "This notebook demonstrates the core features of the Sift Python client:\n", "- Initializing the Sift client\n", @@ -80,11 +80,11 @@ { "data": { "text/html": [ - "
\u2713 Sift client initialized successfully\n",
+       "
✓ Sift client initialized successfully\n",
        "
\n" ], "text/plain": [ - "\u2713 Sift client initialized successfully\n" + "✓ Sift client initialized successfully\n" ] }, "metadata": {}, @@ -100,7 +100,7 @@ "\n", "client = SiftClient(api_key=api_key, grpc_url=grpc_url, rest_url=rest_url)\n", "\n", - "print(\"\u2713 Sift client initialized successfully\")" + "print(\"✓ Sift client initialized successfully\")" ] }, { @@ -1018,12 +1018,12 @@ "data": { "text/html": [ "
\n",
-       "\u2713 Retrieved data for 4 channels:\n",
+       "✓ Retrieved data for 4 channels:\n",
        "
\n" ], "text/plain": [ "\n", - "\u2713 Retrieved data for \u001b[1;36m4\u001b[0m channels:\n" + "✓ Retrieved data for \u001b[1;36m4\u001b[0m channels:\n" ] }, "metadata": {}, @@ -1247,7 +1247,7 @@ " limit=1000, # Limit to 1000 data points per channel\n", ")\n", "\n", - "print(f\"\\n\u2713 Retrieved data for {len(data)} channels:\")\n", + "print(f\"\\n✓ Retrieved data for {len(data)} channels:\")\n", "for channel_name, df in data.items():\n", " print(f\"\\n Channel: {channel_name}\")\n", " print(f\" Data points: {len(df)}\")\n", @@ -1494,12 +1494,12 @@ "data": { "text/html": [ "
\n",
-       "\u2713 Example complete!\n",
+       "✓ Example complete!\n",
        "
\n" ], "text/plain": [ "\n", - "\u2713 Example complete!\n" + "✓ Example complete!\n" ] }, "metadata": {}, @@ -1514,7 +1514,7 @@ " calc_channel.archive()\n", " print(f\"Archived calculated channel: {calc_channel.name}\")\n", "\n", - "print(\"\\n\u2713 Example complete!\")" + "print(\"\\n✓ Example complete!\")" ] } ], @@ -1540,4 +1540,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +}