diff --git a/python/docs/examples/basic.ipynb b/python/docs/examples/basic.ipynb new file mode 100644 index 000000000..d2ebae108 --- /dev/null +++ b/python/docs/examples/basic.ipynb @@ -0,0 +1,1661 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "header", + "metadata": {}, + "source": [ + "# Sift Client Basic Example\n", + "\n", + "This notebook demonstrates the core features of the Sift Python client:\n", + "- Initializing the Sift client\n", + "- Finding, creating, and updating resources\n", + "- Searching channels\n", + "- Pulling data\n", + "- Creating calculated channels" + ] + }, + { + "cell_type": "markdown", + "id": "ef259d05fa338580", + "metadata": {}, + "source": [ + "## Running this notebook\n", + "\n", + "This notebook is written in Jupyter Notebook format and can be run in any Jupyter environment.\n", + "\n", + "Some additional package prerequisites are required to run this notebook\n", + "- `notebook` for running Jupyter Notebooks\n", + "- `python-dotenv` for loading environment variables\n", + "- `rich` for pretty-printing output\n", + "- `pandas` for data manipulation and analysis\n", + "- `matplotlib` for data visualization\n", + "\n", + "You can install these packages using `pip install notebook rich python-dotenv pandas matplotlib`." + ] + }, + { + "cell_type": "markdown", + "id": "f2bbc03e85aa2973", + "metadata": {}, + "source": [ + "## Setup and Initialization\n", + "\n", + "First, import the necessary modules and initialize the Sift client with your credentials.\n", + "\n", + "Best practice is to access credentials using environment variables or a `.env` file with `python-dotenv`. Avoid hardcoding your API in any code you write." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "497279d83878122e", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:38.990325Z", + "start_time": "2025-10-10T21:43:38.928304Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from datetime import datetime, timedelta\n", + "\n", + "from dotenv import load_dotenv\n", + "from rich import print\n", + "from sift_client import SiftClient" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "3492e85e32f1f568", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.071502Z", + "start_time": "2025-10-10T21:43:39.006349Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
✓ Sift client initialized successfully\n",
+       "
\n" + ], + "text/plain": [ + "✓ Sift client initialized successfully\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Get our environment variables\n", + "load_dotenv() # Load environment variables from .env file\n", + "api_key = os.getenv(\"SIFT_API_KEY\")\n", + "grpc_url = os.getenv(\"SIFT_GRPC_URI\")\n", + "rest_url = os.getenv(\"SIFT_REST_URI\")\n", + "\n", + "client = SiftClient(api_key=api_key, grpc_url=grpc_url, rest_url=rest_url)\n", + "\n", + "print(\"✓ Sift client initialized successfully\")" + ] + }, + { + "cell_type": "markdown", + "id": "378db56e29b1a355", + "metadata": {}, + "source": [ + "## Sift Resources\n", + "\n", + "Sift objects, such as Assets, Runs, etc. are all accessed via their API resources.\n", + "\n", + "The [SiftClient](../../reference/sift_client/#sift_client.SiftClient) class provides these resources as properties\n", + "\n", + "* `assets`\n", + "* `runs`\n", + "* etc.\n", + "\n", + "Asynchronous versions are also available by accessing the `async_` property of the client. For example\n", + "\n", + "* `client.async_.assets`\n", + "* `client.async_.runs`\n", + "* etc.\n", + "\n", + "For example, the `Ping` resource can be used for a basic health check." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "fb2db56e076eabec", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:44:59.942480Z", + "start_time": "2025-10-10T21:44:59.885641Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello from Sift!'" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.ping.ping()" + ] + }, + { + "cell_type": "markdown", + "id": "25d1ee945819d7ec", + "metadata": {}, + "source": [ + "## Assets and Runs\n", + "\n", + "Assets represent physical or logical entities in your system (e.g., vehicles, machines, devices). Runs represent time-bounded operational periods for an asset (e.g., a flight, a test, a mission)." + ] + }, + { + "cell_type": "markdown", + "id": "894e74a9efdc603e", + "metadata": {}, + "source": [ + "Resources generally offer similar interaction patterns and methods. For example, the `AssetsAPI` has\n", + "* `get`\n", + "* `list_`\n", + "* `find`\n", + "* `update`\n", + "* `archive`\n", + "* `unarchive`\n", + "\n", + "Other resources may offer additional methods such as `create`.\n", + "\n", + "These resource methods operate on and will return Sift object types. More on tehse can be found here: [sift_types](../../reference/sift_client/sift_types/)" + ] + }, + { + "cell_type": "markdown", + "id": "86b7b129ccbb2956", + "metadata": {}, + "source": [ + "## Listing, Finding, and Getting\n", + "\n", + "`list_` can be used to retrieve objects that match a specific set of criteria:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "49992189f605c3e4", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.721956Z", + "start_time": "2025-10-10T21:43:39.665955Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Name: MarsRoverIngestPusher, ID: 429b864b-0911-4e23-b9d1-2a1fba4d441c\n",
+       "
\n" + ], + "text/plain": [ + "Name: MarsRoverIngestPusher, ID: \u001b[93m429b864b-0911-4e23-b9d1-2a1fba4d441c\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Name: Mars Rover [Jonno export test], ID: 5d86ed46-dec6-41c8-8680-a0ba02d9e546\n",
+       "
\n" + ], + "text/plain": [ + "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" + }, + { + "data": { + "text/html": [ + "
Name: MarsRover_pb4, ID: 9bb3bfec-840d-40b7-a5ab-25591f9a1b38\n",
+       "
\n" + ], + "text/plain": [ + "Name: MarsRover_pb4, ID: \u001b[93m9bb3bfec-840d-40b7-a5ab-25591f9a1b38\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Name: MarsRover, ID: 611914d3-ffb1-402e-ae1e-5eb3e66dea7c\n",
+       "
\n" + ], + "text/plain": [ + "Name: MarsRover, ID: \u001b[93m611914d3-ffb1-402e-ae1e-5eb3e66dea7c\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Name: MarsRover42NaN, ID: 0ad88099-1aea-461c-91b5-0a91c7811f74\n",
+       "
\n" + ], + "text/plain": [ + "Name: MarsRover42NaN, ID: \u001b[93m0ad88099-1aea-461c-91b5-0a91c7811f74\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# List all assets (limited to 10 for this example)\n", + "assets = client.assets.list_(name_contains=\"Mars\", limit=5)\n", + "for asset in assets:\n", + " print(f\"Name: {asset.name}, ID: {asset.id_}\")" + ] + }, + { + "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_`." + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "8b4b9ce9b806a576", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.790539Z", + "start_time": "2025-10-10T21:43:39.732441Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Asset(\n",
+       "    id_='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    name='MarsRover0',\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    tags=['simulator', 'speed-test'],\n",
+       "    metadata={'vehicle_type': 'rover', 'vehicle_version': 123123123.0, 'version_active': True},\n",
+       "    is_archived=False,\n",
+       "    archived_date=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mAsset\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'MarsRover0'\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mtags\u001b[0m=\u001b[1m[\u001b[0m\u001b[32m'simulator'\u001b[0m, \u001b[32m'speed-test'\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'vehicle_type'\u001b[0m: \u001b[32m'rover'\u001b[0m, \u001b[32m'vehicle_version'\u001b[0m: \u001b[1;36m123123123.0\u001b[0m, \u001b[32m'version_active'\u001b[0m: \u001b[3;92mTrue\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m1970\u001b[0m, \u001b[1;36m1\u001b[0m, \u001b[1;36m1\u001b[0m, \u001b[1;36m0\u001b[0m, \u001b[1;36m0\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Find a specific asset by name\n", + "asset_name = \"MarsRover0\"\n", + "asset = client.assets.find(name=asset_name)\n", + "print(asset)" + ] + }, + { + "cell_type": "markdown", + "id": "1df0e447f3a04c98", + "metadata": {}, + "source": "When we know exactly what we are looking for, we can use `get`." + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "d8c650d021ff4af8", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.857266Z", + "start_time": "2025-10-10T21:43:39.798877Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Asset(\n",
+       "    id_='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    name='MarsRover0',\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    tags=['simulator', 'speed-test'],\n",
+       "    metadata={'vehicle_type': 'rover', 'vehicle_version': 123123123.0, 'version_active': True},\n",
+       "    is_archived=False,\n",
+       "    archived_date=datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mAsset\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'MarsRover0'\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mtags\u001b[0m=\u001b[1m[\u001b[0m\u001b[32m'simulator'\u001b[0m, \u001b[32m'speed-test'\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'vehicle_type'\u001b[0m: \u001b[32m'rover'\u001b[0m, \u001b[32m'vehicle_version'\u001b[0m: \u001b[1;36m123123123.0\u001b[0m, \u001b[32m'version_active'\u001b[0m: \u001b[3;92mTrue\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m1970\u001b[0m, \u001b[1;36m1\u001b[0m, \u001b[1;36m1\u001b[0m, \u001b[1;36m0\u001b[0m, \u001b[1;36m0\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Get the exact asset by ID\n", + "asset = client.assets.get(asset_id=asset.id_)\n", + "print(asset)" + ] + }, + { + "cell_type": "markdown", + "id": "e8ebf9754df8f646", + "metadata": {}, + "source": [ + "## Creating, Updating, and Archiving\n", + "\n", + "Most resources offer `create`, `update`, and `archive` methods.\n", + "\n", + "Since `create` returns a `Run`, we can chain `update` and `archive` on it and the Sift object will update in-place." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "143bafd4b2a5edb5", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.914350Z", + "start_time": "2025-10-10T21:43:39.860934Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Run(\n",
+       "    id_='523c6f2a-181c-4ee7-8876-421901daf97f',\n",
+       "    name='Test Run',\n",
+       "    description='A test run',\n",
+       "    created_date=datetime.datetime(2025, 10, 10, 21, 43, 39, 913134, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 10, 10, 21, 43, 39, 915093, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    modified_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    metadata={},\n",
+       "    tags=[],\n",
+       "    asset_ids=[],\n",
+       "    is_adhoc=False,\n",
+       "    is_archived=False,\n",
+       "    start_time=datetime.datetime(2025, 10, 10, 14, 43, 39, 861550, tzinfo=datetime.timezone.utc),\n",
+       "    stop_time=None,\n",
+       "    duration=datetime.timedelta(seconds=25200, microseconds=54929),\n",
+       "    default_report_id='854787b6-4e38-4c81-bdf0-8d143c791d01',\n",
+       "    client_key=None,\n",
+       "    archived_date=None\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mRun\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'523c6f2a-181c-4ee7-8876-421901daf97f'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'Test Run'\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'A test run'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m913134\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m915093\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33mtags\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33masset_ids\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mis_adhoc\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33mstart_time\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m14\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m861550\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mstop_time\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33mduration\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.timedelta\u001b[0m\u001b[1m(\u001b[0m\u001b[33mseconds\u001b[0m=\u001b[1;36m25200\u001b[0m, \u001b[33mmicroseconds\u001b[0m=\u001b[1;36m54929\u001b[0m\u001b[1m)\u001b[0m,\n", + " \u001b[33mdefault_report_id\u001b[0m=\u001b[32m'854787b6-4e38-4c81-bdf0-8d143c791d01'\u001b[0m,\n", + " \u001b[33mclient_key\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[3;35mNone\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run creation\n", + "run = client.runs.create(\n", + " dict(\n", + " name=\"Test Run\", description=\"A test run\", asset_ids=[asset.id_], start_time=datetime.now()\n", + " )\n", + ")\n", + "print(run)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "22ad6199aeb8cc5d", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:39.983767Z", + "start_time": "2025-10-10T21:43:39.922700Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Run(\n",
+       "    id_='523c6f2a-181c-4ee7-8876-421901daf97f',\n",
+       "    name='Updated Test Run',\n",
+       "    description='An updated test run',\n",
+       "    created_date=datetime.datetime(2025, 10, 10, 21, 43, 39, 913134, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 10, 10, 21, 43, 39, 981793, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    modified_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    metadata={},\n",
+       "    tags=[],\n",
+       "    asset_ids=[],\n",
+       "    is_adhoc=False,\n",
+       "    is_archived=False,\n",
+       "    start_time=datetime.datetime(2025, 10, 10, 14, 43, 39, 861550, tzinfo=datetime.timezone.utc),\n",
+       "    stop_time=None,\n",
+       "    duration=datetime.timedelta(seconds=25200, microseconds=123957),\n",
+       "    default_report_id='854787b6-4e38-4c81-bdf0-8d143c791d01',\n",
+       "    client_key=None,\n",
+       "    archived_date=None\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mRun\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'523c6f2a-181c-4ee7-8876-421901daf97f'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'Updated Test Run'\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'An updated test run'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m913134\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m981793\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33mtags\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33masset_ids\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mis_adhoc\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33mstart_time\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m14\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m861550\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mstop_time\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33mduration\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.timedelta\u001b[0m\u001b[1m(\u001b[0m\u001b[33mseconds\u001b[0m=\u001b[1;36m25200\u001b[0m, \u001b[33mmicroseconds\u001b[0m=\u001b[1;36m123957\u001b[0m\u001b[1m)\u001b[0m,\n", + " \u001b[33mdefault_report_id\u001b[0m=\u001b[32m'854787b6-4e38-4c81-bdf0-8d143c791d01'\u001b[0m,\n", + " \u001b[33mclient_key\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[3;35mNone\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run update\n", + "run.update(\n", + " dict(\n", + " name=\"Updated Test Run\",\n", + " description=\"An updated test run\",\n", + " )\n", + ")\n", + "print(run)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "3d24afef7771f010", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:40.057415Z", + "start_time": "2025-10-10T21:43:39.988773Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Run(\n",
+       "    id_='523c6f2a-181c-4ee7-8876-421901daf97f',\n",
+       "    name='Updated Test Run',\n",
+       "    description='An updated test run',\n",
+       "    created_date=datetime.datetime(2025, 10, 10, 21, 43, 39, 913134, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 10, 10, 21, 43, 40, 52001, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    modified_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    metadata={},\n",
+       "    tags=[],\n",
+       "    asset_ids=[],\n",
+       "    is_adhoc=False,\n",
+       "    is_archived=True,\n",
+       "    start_time=datetime.datetime(2025, 10, 10, 14, 43, 39, 861550, tzinfo=datetime.timezone.utc),\n",
+       "    stop_time=None,\n",
+       "    duration=datetime.timedelta(seconds=25200, microseconds=196350),\n",
+       "    default_report_id='854787b6-4e38-4c81-bdf0-8d143c791d01',\n",
+       "    client_key=None,\n",
+       "    archived_date=datetime.datetime(2025, 10, 10, 21, 43, 40, 48186, tzinfo=datetime.timezone.utc)\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mRun\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'523c6f2a-181c-4ee7-8876-421901daf97f'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'Updated Test Run'\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'An updated test run'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m913134\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m40\u001b[0m, \u001b[1;36m52001\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33mtags\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33masset_ids\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mis_adhoc\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;92mTrue\u001b[0m,\n", + " \u001b[33mstart_time\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m14\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m39\u001b[0m, \u001b[1;36m861550\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mstop_time\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33mduration\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.timedelta\u001b[0m\u001b[1m(\u001b[0m\u001b[33mseconds\u001b[0m=\u001b[1;36m25200\u001b[0m, \u001b[33mmicroseconds\u001b[0m=\u001b[1;36m196350\u001b[0m\u001b[1m)\u001b[0m,\n", + " \u001b[33mdefault_report_id\u001b[0m=\u001b[32m'854787b6-4e38-4c81-bdf0-8d143c791d01'\u001b[0m,\n", + " \u001b[33mclient_key\u001b[0m=\u001b[3;35mNone\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m43\u001b[0m, \u001b[1;36m40\u001b[0m, \u001b[1;36m48186\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Run archive\n", + "run.archive()\n", + "print(run)" + ] + }, + { + "cell_type": "markdown", + "id": "c1830f179b22cb43", + "metadata": {}, + "source": [ + "## Searching Channels\n", + "\n", + "Channels represent time-series data streams (e.g., sensor readings, telemetry)." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "9c5a1b60c0fce9fc", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:40.313373Z", + "start_time": "2025-10-10T21:43:40.062560Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Found 5 channels for asset 'MarsRover0':\n",
+       "
\n" + ], + "text/plain": [ + "Found \u001b[1;36m5\u001b[0m channels for asset \u001b[32m'MarsRover0'\u001b[0m:\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='0f1a32d4-0f97-494f-82f8-2ed7f3983e74',\n",
+       "    name='is_even',\n",
+       "    data_type=<ChannelDataType.BOOL: 5>,\n",
+       "    description='is the value of milliseconds even',\n",
+       "    unit='0c359676-c6c9-47d3-acca-d8e938311b2a',\n",
+       "    bit_field_elements=[],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'0f1a32d4-0f97-494f-82f8-2ed7f3983e74'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'is_even'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.BOOL:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m5\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'is the value of milliseconds even'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'0c359676-c6c9-47d3-acca-d8e938311b2a'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='1c2dd815-2c9e-4801-92dc-1f65cad2114b',\n",
+       "    name='voltage',\n",
+       "    data_type=<ChannelDataType.INT_32: 7>,\n",
+       "    description='voltage at the source',\n",
+       "    unit='09f1005b-df6a-4f82-838f-c57e77c587ef',\n",
+       "    bit_field_elements=[],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'1c2dd815-2c9e-4801-92dc-1f65cad2114b'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'voltage'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.INT_32:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m7\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'voltage at the source'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'09f1005b-df6a-4f82-838f-c57e77c587ef'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='606817a0-396e-4575-9ffc-45a4ef9ca66a',\n",
+       "    name='log',\n",
+       "    data_type=<ChannelDataType.STRING: 2>,\n",
+       "    description='this simulates log files',\n",
+       "    unit='0c359676-c6c9-47d3-acca-d8e938311b2a',\n",
+       "    bit_field_elements=[],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'606817a0-396e-4575-9ffc-45a4ef9ca66a'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'log'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.STRING:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'this simulates log files'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'0c359676-c6c9-47d3-acca-d8e938311b2a'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='a59c4ec4-5e8c-4457-8a41-ecbc06b0dca7',\n",
+       "    name='mainmotor.velocity',\n",
+       "    data_type=<ChannelDataType.DOUBLE: 1>,\n",
+       "    description='speed',\n",
+       "    unit='b840c8e8-33fb-433c-9448-07577f04e990',\n",
+       "    bit_field_elements=[],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'a59c4ec4-5e8c-4457-8a41-ecbc06b0dca7'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'mainmotor.velocity'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.DOUBLE:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'speed'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'b840c8e8-33fb-433c-9448-07577f04e990'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='c052cef4-d3eb-486e-b3cb-63b59504e913',\n",
+       "    name='gpio',\n",
+       "    data_type=<ChannelDataType.BIT_FIELD: 4>,\n",
+       "    description='on/off values for pins on gpio',\n",
+       "    unit='0c359676-c6c9-47d3-acca-d8e938311b2a',\n",
+       "    bit_field_elements=[\n",
+       "        {'name': '12v', 'index': 0, 'bit_count': 1},\n",
+       "        {'name': 'charge', 'index': 1, 'bit_count': 2},\n",
+       "        {'name': 'led', 'index': 3, 'bit_count': 4},\n",
+       "        {'name': 'heater', 'index': 7, 'bit_count': 1}\n",
+       "    ],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'c052cef4-d3eb-486e-b3cb-63b59504e913'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'gpio'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.BIT_FIELD:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m4\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'on/off values for pins on gpio'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'0c359676-c6c9-47d3-acca-d8e938311b2a'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\n", + " \u001b[1m{\u001b[0m\u001b[32m'name'\u001b[0m: \u001b[32m'12v'\u001b[0m, \u001b[32m'index'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'bit_count'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[1m{\u001b[0m\u001b[32m'name'\u001b[0m: \u001b[32m'charge'\u001b[0m, \u001b[32m'index'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'bit_count'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[1m{\u001b[0m\u001b[32m'name'\u001b[0m: \u001b[32m'led'\u001b[0m, \u001b[32m'index'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'bit_count'\u001b[0m: \u001b[1;36m4\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[1m{\u001b[0m\u001b[32m'name'\u001b[0m: \u001b[32m'heater'\u001b[0m, \u001b[32m'index'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'bit_count'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m\n", + " \u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# List channels for the selected asset\n", + "channels = client.channels.list_(asset=asset.id_, limit=5)\n", + "\n", + "print(f\"Found {len(channels)} channels for asset '{asset.name}':\")\n", + "for channel in channels: # Show first 10\n", + " print(channel)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "c2317d7f561926d6", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:40.414219Z", + "start_time": "2025-10-10T21:43:40.325320Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Channels containing 'velocity': 1\n",
+       "
\n" + ], + "text/plain": [ + "Channels containing \u001b[32m'velocity'\u001b[0m: \u001b[1;36m1\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
Channel(\n",
+       "    id_='a59c4ec4-5e8c-4457-8a41-ecbc06b0dca7',\n",
+       "    name='mainmotor.velocity',\n",
+       "    data_type=<ChannelDataType.DOUBLE: 1>,\n",
+       "    description='speed',\n",
+       "    unit='b840c8e8-33fb-433c-9448-07577f04e990',\n",
+       "    bit_field_elements=[],\n",
+       "    enum_types={},\n",
+       "    asset_id='61d6e4f0-8287-4678-b071-18a95fcd9db6',\n",
+       "    created_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 3, 4, 19, 51, 15, 89746, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d',\n",
+       "    modified_by_user_id='f47e4854-234b-421c-badb-7f8bb757cd9d'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'a59c4ec4-5e8c-4457-8a41-ecbc06b0dca7'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'mainmotor.velocity'\u001b[0m,\n", + " \u001b[33mdata_type\u001b[0m=\u001b[1m<\u001b[0m\u001b[1;95mChannelDataType.DOUBLE:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'speed'\u001b[0m,\n", + " \u001b[33munit\u001b[0m=\u001b[32m'b840c8e8-33fb-433c-9448-07577f04e990'\u001b[0m,\n", + " \u001b[33mbit_field_elements\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33menum_types\u001b[0m=\u001b[1m{\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[33masset_id\u001b[0m=\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m4\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m51\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m89746\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'f47e4854-234b-421c-badb-7f8bb757cd9d'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Search for specific channels by name pattern\n", + "# Replace with a pattern that matches your channel names\n", + "velocity_channels = client.channels.list_(asset=asset.id_, name_contains=\"velocity\", limit=10)\n", + "\n", + "print(f\"Channels containing 'velocity': {len(velocity_channels)}\")\n", + "for ch in velocity_channels:\n", + " print(ch)" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "1601a55853281e95", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:40.482064Z", + "start_time": "2025-10-10T21:43:40.429852Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Channels in run 'Updated Test Run': 0\n",
+       "
\n" + ], + "text/plain": [ + "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" + } + ], + "source": [ + "# Get channels for a specific run. There should be none since we just created it.\n", + "if run:\n", + " run_channels = client.channels.list_(run=run.id_, limit=10)\n", + " print(f\"Channels in run '{run.name}': {len(run_channels)}\")\n", + " for ch in run_channels:\n", + " print(f\" - {ch.name}\")" + ] + }, + { + "cell_type": "markdown", + "id": "7c611ee731605cbd", + "metadata": {}, + "source": [ + "## Pulling Data\n", + "\n", + "Retrieve time-series data from channels as pandas DataFrames." + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "b6b40678b0bbe34e", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:40.980438Z", + "start_time": "2025-10-10T21:43:40.491510Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n",
+       "✓ Retrieved data for 4 channels:\n",
+       "
\n" + ], + "text/plain": [ + "\n", + "✓ Retrieved data for \u001b[1;36m4\u001b[0m channels:\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n",
+       "  Channel: is_even\n",
+       "
\n" + ], + "text/plain": [ + "\n", + " Channel: is_even\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
  Data points: 1000\n",
+       "
\n" + ], + "text/plain": [ + " Data points: \u001b[1;36m1000\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
                                     is_even\n",
+       "2025-06-04 17:21:13.180486958+00:00     True\n",
+       "2025-06-04 17:21:13.380486958+00:00     True\n",
+       "2025-06-04 17:21:13.580486958+00:00     True\n",
+       "2025-06-04 17:21:13.780486958+00:00     True\n",
+       "2025-06-04 17:21:13.980486958+00:00     True\n",
+       "
\n" + ], + "text/plain": [ + " is_even\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m180486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[3;92mTrue\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m380486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[3;92mTrue\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m580486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[3;92mTrue\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m780486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[3;92mTrue\u001b[0m\n", + "\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" + }, + { + "data": { + "text/html": [ + "
\n",
+       "  Channel: mainmotor.velocity\n",
+       "
\n" + ], + "text/plain": [ + "\n", + " Channel: mainmotor.velocity\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
  Data points: 1000\n",
+       "
\n" + ], + "text/plain": [ + " Data points: \u001b[1;36m1000\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
                                     mainmotor.velocity\n",
+       "2025-06-04 17:21:13.180486958+00:00           21.352492\n",
+       "2025-06-04 17:21:13.380486958+00:00           32.011998\n",
+       "2025-06-04 17:21:13.580486958+00:00           34.370449\n",
+       "2025-06-04 17:21:13.780486958+00:00           25.635827\n",
+       "2025-06-04 17:21:13.980486958+00:00           16.148490\n",
+       "
\n" + ], + "text/plain": [ + " mainmotor.velocity\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m180486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m21.352492\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m380486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m32.011998\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m580486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m34.370449\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m780486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m25.635827\u001b[0m\n", + "\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" + }, + { + "data": { + "text/html": [ + "
\n",
+       "  Channel: vehicle_state\n",
+       "
\n" + ], + "text/plain": [ + "\n", + " Channel: vehicle_state\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
  Data points: 1000\n",
+       "
\n" + ], + "text/plain": [ + " Data points: \u001b[1;36m1000\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
                                     vehicle_state\n",
+       "2025-06-04 17:21:13.180486958+00:00              1\n",
+       "2025-06-04 17:21:13.380486958+00:00              1\n",
+       "2025-06-04 17:21:13.580486958+00:00              1\n",
+       "2025-06-04 17:21:13.780486958+00:00              1\n",
+       "2025-06-04 17:21:13.980486958+00:00              1\n",
+       "
\n" + ], + "text/plain": [ + " vehicle_state\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m180486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m1\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m380486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m1\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m580486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m1\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m780486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m1\u001b[0m\n", + "\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" + }, + { + "data": { + "text/html": [ + "
\n",
+       "  Channel: voltage\n",
+       "
\n" + ], + "text/plain": [ + "\n", + " Channel: voltage\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
  Data points: 1000\n",
+       "
\n" + ], + "text/plain": [ + " Data points: \u001b[1;36m1000\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
                                     voltage\n",
+       "2025-06-04 17:21:13.180486958+00:00       12\n",
+       "2025-06-04 17:21:13.380486958+00:00       12\n",
+       "2025-06-04 17:21:13.580486958+00:00       12\n",
+       "2025-06-04 17:21:13.780486958+00:00        2\n",
+       "2025-06-04 17:21:13.980486958+00:00        2\n",
+       "
\n" + ], + "text/plain": [ + " voltage\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m180486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m12\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m380486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m12\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m580486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m12\u001b[0m\n", + "\u001b[1;36m2025\u001b[0m-\u001b[1;36m06\u001b[0m-\u001b[1;36m04\u001b[0m \u001b[1;92m17:21:13\u001b[0m.\u001b[1;36m780486958\u001b[0m+\u001b[1;92m00:00\u001b[0m \u001b[1;36m2\u001b[0m\n", + "\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" + } + ], + "source": [ + "run = client.runs.list_(\n", + " duration_greater_than=timedelta(seconds=30),\n", + " description_contains=\"simulated run: 1 flows, 8 total channels, 5hz sampling rate\",\n", + ")[0]\n", + "\n", + "# Get data as a dictionary of pandas DataFrames\n", + "data = client.channels.get_data(\n", + " channels=client.channels.list_(run=run, name_contains=\"v\"),\n", + " run=run,\n", + " limit=1000, # Limit to 1000 data points per channel\n", + ")\n", + "\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", + " print(df.head())" + ] + }, + { + "cell_type": "markdown", + "id": "b6c81e31a31fdc03", + "metadata": {}, + "source": [ + "## Creating Calculated Channels\n", + "\n", + "Calculated channels allow you to create derived metrics from existing channels using mathematical expressions." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "70a5b0d39e5230c4", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T22:56:15.431423Z", + "start_time": "2025-10-10T22:56:15.154065Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Calculated channel 'is_even_per_voltage' already exists\n",
+       "
\n" + ], + "text/plain": [ + "Calculated channel \u001b[32m'is_even_per_voltage'\u001b[0m already exists\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
CalculatedChannel(\n",
+       "    id_='d283b5fe-fed5-4260-93d1-8bd65bec4bfe',\n",
+       "    name='is_even_per_voltage',\n",
+       "    description='Ratio of is_even to voltage',\n",
+       "    expression='$1 / $2',\n",
+       "    channel_references=[\n",
+       "        {'channel_reference': '$1', 'channel_identifier': 'is_even'},\n",
+       "        {'channel_reference': '$2', 'channel_identifier': 'voltage'}\n",
+       "    ],\n",
+       "    is_archived=True,\n",
+       "    units='',\n",
+       "    asset_ids=['61d6e4f0-8287-4678-b071-18a95fcd9db6'],\n",
+       "    tag_ids=[],\n",
+       "    all_assets=False,\n",
+       "    organization_id='dd9f82ef-7805-4b02-9572-ec61b71edde6',\n",
+       "    client_key='',\n",
+       "    archived_date=datetime.datetime(2025, 10, 10, 21, 36, 19, 947790, tzinfo=datetime.timezone.utc),\n",
+       "    version_id='0923ec64-9799-4e73-8038-71496227a846',\n",
+       "    version=1,\n",
+       "    change_message='Created calculated channel',\n",
+       "    user_notes='',\n",
+       "    created_date=datetime.datetime(2025, 10, 10, 21, 34, 3, 533939, tzinfo=datetime.timezone.utc),\n",
+       "    modified_date=datetime.datetime(2025, 10, 10, 21, 34, 3, 533939, tzinfo=datetime.timezone.utc),\n",
+       "    created_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c',\n",
+       "    modified_by_user_id='ae1f6c7c-3e93-40a9-8796-d227a725662c'\n",
+       ")\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35mCalculatedChannel\u001b[0m\u001b[1m(\u001b[0m\n", + " \u001b[33mid_\u001b[0m=\u001b[32m'd283b5fe-fed5-4260-93d1-8bd65bec4bfe'\u001b[0m,\n", + " \u001b[33mname\u001b[0m=\u001b[32m'is_even_per_voltage'\u001b[0m,\n", + " \u001b[33mdescription\u001b[0m=\u001b[32m'Ratio of is_even to voltage'\u001b[0m,\n", + " \u001b[33mexpression\u001b[0m=\u001b[32m'$1 / $2'\u001b[0m,\n", + " \u001b[33mchannel_references\u001b[0m=\u001b[1m[\u001b[0m\n", + " \u001b[1m{\u001b[0m\u001b[32m'channel_reference'\u001b[0m: \u001b[32m'$1'\u001b[0m, \u001b[32m'channel_identifier'\u001b[0m: \u001b[32m'is_even'\u001b[0m\u001b[1m}\u001b[0m,\n", + " \u001b[1m{\u001b[0m\u001b[32m'channel_reference'\u001b[0m: \u001b[32m'$2'\u001b[0m, \u001b[32m'channel_identifier'\u001b[0m: \u001b[32m'voltage'\u001b[0m\u001b[1m}\u001b[0m\n", + " \u001b[1m]\u001b[0m,\n", + " \u001b[33mis_archived\u001b[0m=\u001b[3;92mTrue\u001b[0m,\n", + " \u001b[33munits\u001b[0m=\u001b[32m''\u001b[0m,\n", + " \u001b[33masset_ids\u001b[0m=\u001b[1m[\u001b[0m\u001b[32m'61d6e4f0-8287-4678-b071-18a95fcd9db6'\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mtag_ids\u001b[0m=\u001b[1m[\u001b[0m\u001b[1m]\u001b[0m,\n", + " \u001b[33mall_assets\u001b[0m=\u001b[3;91mFalse\u001b[0m,\n", + " \u001b[33morganization_id\u001b[0m=\u001b[32m'dd9f82ef-7805-4b02-9572-ec61b71edde6'\u001b[0m,\n", + " \u001b[33mclient_key\u001b[0m=\u001b[32m''\u001b[0m,\n", + " \u001b[33marchived_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m36\u001b[0m, \u001b[1;36m19\u001b[0m, \u001b[1;36m947790\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mversion_id\u001b[0m=\u001b[32m'0923ec64-9799-4e73-8038-71496227a846'\u001b[0m,\n", + " \u001b[33mversion\u001b[0m=\u001b[1;36m1\u001b[0m,\n", + " \u001b[33mchange_message\u001b[0m=\u001b[32m'Created calculated channel'\u001b[0m,\n", + " \u001b[33muser_notes\u001b[0m=\u001b[32m''\u001b[0m,\n", + " \u001b[33mcreated_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m34\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m533939\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mmodified_date\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m10\u001b[0m, \u001b[1;36m21\u001b[0m, \u001b[1;36m34\u001b[0m, \u001b[1;36m3\u001b[0m, \u001b[1;36m533939\u001b[0m, \u001b[33mtzinfo\u001b[0m=\u001b[35mdatetime\u001b[0m.timezone.utc\u001b[1m)\u001b[0m,\n", + " \u001b[33mcreated_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m,\n", + " \u001b[33mmodified_by_user_id\u001b[0m=\u001b[32m'ae1f6c7c-3e93-40a9-8796-d227a725662c'\u001b[0m\n", + "\u001b[1m)\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Create a calculated channel\n", + "# This example creates a channel that divides two existing channels\n", + "# Replace channel names with actual channels from your system\n", + "\n", + "\n", + "# Use first two channels for this example\n", + "channel1 = channels[0]\n", + "channel2 = channels[1]\n", + "\n", + "calc_channel_name = f\"{channel1.name}_per_{channel2.name}\"\n", + "\n", + "# Check if calculated channel already exists\n", + "existing = client.calculated_channels.find(\n", + " name=calc_channel_name, asset=asset.id_, include_archived=True\n", + ")\n", + "\n", + "if existing:\n", + " print(f\"Calculated channel '{calc_channel_name}' already exists\")\n", + " calc_channel = existing\n", + "else:\n", + " print(f\"Creating calculated channel: {calc_channel_name}\")\n", + "\n", + " calc_channel = client.calculated_channels.create(\n", + " dict(\n", + " name=calc_channel_name,\n", + " description=f\"Ratio of {channel1.name} to {channel2.name}\",\n", + " expression=\"$1 / $2\", # $1 and $2 refer to the channel references below\n", + " expression_channel_references=[\n", + " dict(channel_reference=\"$1\", channel_identifier=channel1.name),\n", + " dict(channel_reference=\"$2\", channel_identifier=channel2.name),\n", + " ],\n", + " asset_ids=[asset.id_],\n", + " )\n", + " )\n", + "\n", + "print(calc_channel)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "c3be1f869e83d59d", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:41.257980Z", + "start_time": "2025-10-10T21:35:07.170355Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Calculated channels for asset 'MarsRover0': 1\n",
+       "
\n" + ], + "text/plain": [ + "Calculated channels for asset \u001b[32m'MarsRover0'\u001b[0m: \u001b[1;36m1\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
  - is_even_per_voltage\n",
+       "
\n" + ], + "text/plain": [ + " - is_even_per_voltage\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
    Expression: $1 / $2\n",
+       "
\n" + ], + "text/plain": [ + " 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" + }, + { + "data": { + "text/html": [ + "
    Version: 1\n",
+       "
\n" + ], + "text/plain": [ + " Version: \u001b[1;36m1\u001b[0m\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# List all calculated channels for the asset\n", + "calc_channels = client.calculated_channels.list_(asset=asset.id_, name=calc_channel_name, limit=10)\n", + "\n", + "print(f\"Calculated channels for asset '{asset.name}': {len(calc_channels)}\")\n", + "for cc in calc_channels:\n", + " print(f\" - {cc.name}\")\n", + " print(f\" Expression: {cc.expression}\")\n", + " print(f\" Version: {cc.version}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "37b76600aedba1a1", + "metadata": { + "ExecuteTime": { + "end_time": "2025-10-10T21:43:41.258564Z", + "start_time": "2025-10-10T21:36:19.888164Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
Archived calculated channel: is_even_per_voltage\n",
+       "
\n" + ], + "text/plain": [ + "Archived calculated channel: is_even_per_voltage\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n",
+       "✓ Example complete!\n",
+       "
\n" + ], + "text/plain": [ + "\n", + "✓ Example complete!\n" + ] + }, + "jetTransient": { + "display_id": null + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Optional: Clean up resources\n", + "# Uncomment to archive the created calculated channel and rule\n", + "\n", + "if calc_channel:\n", + " calc_channel.archive()\n", + " print(f\"Archived calculated channel: {calc_channel.name}\")\n", + "\n", + "print(\"\\n✓ Example complete!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/docs/overrides/main.html b/python/docs/overrides/main.html index 648b7a6fe..4578fc92c 100644 --- a/python/docs/overrides/main.html +++ b/python/docs/overrides/main.html @@ -8,3 +8,13 @@ Click here to go to latest. {% endblock %} + +{% block content %} +{% if page.nb_url %} + + {% include ".icons/material/download.svg" %} + +{% endif %} + +{{ super() }} +{% endblock content %} diff --git a/python/examples/sift_client/assets/main.py b/python/examples/sift_client/assets/main.py deleted file mode 100644 index 77788bd92..000000000 --- a/python/examples/sift_client/assets/main.py +++ /dev/null @@ -1,27 +0,0 @@ -import os - -from dotenv import load_dotenv -from sift_client.client import SiftClient - - -def main(): - sift = SiftClient( - api_key=apikey, - rest_url="https://api.development.siftstack.com", - grpc_url="https://grpc-api.development.siftstack.com", - ) - - # Find an asset - asset = sift.assets.find(name_contains="Nostromo") - print(asset.tags) - asset.update(dict(tags=["simulator", "my_new_tag"])) - print(asset) - - -if __name__ == "__main__": - load_dotenv() - - apikey = os.getenv("SIFT_API_KEY") - assert apikey, "Missing 'SIFT_API_KEY' environment variable." - - main() diff --git a/python/examples/sift_client/ping/main.py b/python/examples/sift_client/ping/main.py deleted file mode 100644 index 42b1e4d0c..000000000 --- a/python/examples/sift_client/ping/main.py +++ /dev/null @@ -1,27 +0,0 @@ -import asyncio -import os - -from dotenv import load_dotenv -from sift_client.client import SiftClient - - -async def main(client): - print("Async in async loop:", await client.async_.ping.ping()) - print("Sync in async loop:", client.ping.ping()) - - -if __name__ == "__main__": - load_dotenv() - - apikey = os.getenv("SIFT_API_KEY") - assert apikey, "Missing 'SIFT_API_KEY' environment variable." - - sift = SiftClient( - api_key=apikey, - rest_url="https://api.development.siftstack.com", - grpc_url="https://grpc-api.development.siftstack.com", - ) - - asyncio.run(main(sift)) - - print("Sync:", sift.ping.ping()) diff --git a/python/lib/sift_client/__init__.py b/python/lib/sift_client/__init__.py index 5bcf1f5c3..d724932fd 100644 --- a/python/lib/sift_client/__init__.py +++ b/python/lib/sift_client/__init__.py @@ -1,11 +1,17 @@ -"""!!! warning +"""Sift Client Library - Python client for interacting with Sift APIs. + +!!! warning The Sift Client is experimental and is subject to change. +## Overview -# Sift Client Library +This library provides a high-level Python client for interacting with Sift APIs. It offers: -This library provides a high-level Python client for interacting with Sift APIs. It offers both synchronous and -asynchronous interfaces, strong type checking, and a Pythonic API design. +- **Synchronous and asynchronous interfaces** for all operations +- **Strong type checking** with Pydantic models +- **Pythonic API design** with intuitive method names +- **Comprehensive filtering** capabilities for queries +- **Automatic type conversion** between protobuf and Python types ## Installation @@ -13,193 +19,120 @@ pip install sift-stack-py ``` -## Getting Started - -### Initializing the Client +## Quick Start -You can initialize the Sift client with your API key and service URLs: +### Initialize the Client ```python from sift_client import SiftClient -from datetime import datetime -# Initialize with individual parameters +# Initialize with credentials client = SiftClient( api_key="your-api-key", - grpc_url="your-sift-grpc-url", - rest_url="your-sift-rest-url" -) - -# Or use a connection configuration -from sift_client.transport import SiftConnectionConfig - -config = SiftConnectionConfig( - api_key="your-api-key", - grpc_url="your-sift-grpc-url", - rest_url="your-sift-rest-url" + grpc_url="grpc.siftstack.com:443", + rest_url="https://api.siftstack.com" ) -client = SiftClient(connection_config=config) ``` -The `SiftConnectionConfig` provides access to additional configuration options such as `use_ssl` and `cert_via_openssl`. - -### Using Synchronous and Asynchronous APIs - -The Sift client provides both synchronous and asynchronous versions of all APIs. You can choose the one that best fits -your application's needs. - -#### Synchronous API - -The synchronous API is perfect for scripts, notebooks, and applications that don't need asynchronous operation: +### Basic Operations ```python -# Get an asset by ID +# Get an asset asset = client.assets.get(asset_id="asset123") -# List assets with filtering -assets = client.assets.list_( - name_contains="example", - created_after=datetime(2023, 1, 1), - include_archived=False +# List resources with filtering +runs = client.runs.list_( + assets=[asset.id_], + start_time_after=datetime.now() - timedelta(days=7), + limit=10 ) -# Find a single asset matching criteria -asset = client.assets.find(name="my-asset") -``` +# Update a resource +asset.update({"tags": ["production", "v2"]}) -#### Asynchronous API +# Create a new resource +run = client.runs.create({ + "name": "Test Run", + "asset_ids": [asset.id_], + "start_time": datetime.now() +}) +``` -The asynchronous API is ideal for high-performance applications and services that need to make concurrent API calls: +### Async Usage ```python import asyncio +async def main(): + # Use async_ accessor for async operations + asset = await client.async_.assets.get(asset_id="asset123") + runs = await client.async_.runs.list_(limit=10) + return asset, runs -async def get_asset_async(): - # Get an asset by ID asynchronously - asset = await client.assets_async.get(asset_id="asset123") - - # Running Sync within async also works - some_other_asset = client.assets.get(asset_id="asset456") - - return asset - - -# Run in an async context -asset = asyncio.run(get_asset_async()) - -``` - -### Working with Sift Types - -Sift types (like `Asset`, `Run`, etc.) are immutable Pydantic models that provide a convenient interface for working -with Sift resources. - -#### Accessing Properties - -```python -# Get an asset -asset = client.assets.get(asset_id="asset123") - -# Access properties -print(f"Asset name: {asset.name}") -print(f"Created on: {asset.created_date}") -print(f"Tags: {', '.join(asset.tags)}") -print(f"Is archived: {asset.is_archived}") +result = asyncio.run(main()) ``` -#### Using Methods on Sift Types +## Key Components -Sift types have convenient methods for common operations. These methods use the synchronous API internally. -**Using these methods will update the instance in-place.** +### Resources -```python -# Get an asset -asset = client.assets.get(asset_id="asset123") +Resource APIs provide methods for interacting with Sift services. Each resource supports +operations like `get()`, `list_()`, `create()`, `update()`, and `archive()`. -# Archive the asset -asset.archive(archive_runs=True) +**Available Resources:** -# Update the asset -asset.update({ - "tags": ["updated", "example"] -}) -``` +- `client.assets` - Manage physical or logical entities +- `client.runs` - Manage time-bounded operational periods +- etc. -> **Note:** Type methods only work with the synchronous API. If you need to use the asynchronous API, you should use the -> resource APIs directly. +See [resources](resources/) for detailed documentation and a complete list. -#### Creating Update Models +### Types -For more complex updates, you can create update models (instead of a key-value dictionary): +Sift types are immutable Pydantic models representing Sift objects. They provide +type-safe access to properties and convenience methods for common operations. -```python -from sift_client.types.asset import AssetUpdate +**Available Types:** -# Create an update model -update = AssetUpdate(tags=["new", "tags"]) +- `Asset`, `AssetUpdate` - Asset resources +- `Run`, `RunCreate`, `RunUpdate` - Run resources +- etc. -# Apply the update -asset = client.assets.update(asset="asset123", update=update) +See [sift_types](sift_types/) for detailed documentation and a complete list. -# Or using the asset method -asset = client.assets.get(asset_id="asset123").update(update) -``` +## Examples -## Advanced Usage +For complete examples, see the [examples](../examples/) directory. -### Working with Tags +## Connection Configuration -Tags are a powerful way to organize and filter your assets: +For advanced connection options: ```python -# Add tags when updating an asset -asset.update({ - "tags": ["production", "model-v1", "trained"] -}) +from sift_client.transport import SiftConnectionConfig, GrpcConfig, RestConfig -# Filter assets by tags -production_assets = client.assets.list_( - tags=["production"] -) -``` - -### Filtering Assets - -The client provides various ways to filter different Sift types: - -```python -# Filter by name (exact match) -assets = client.assets.list_(name="my-model") - -# Filter by name (contains) -assets = client.assets.list_(name_contains="model") - -# Filter by name (regex) -assets = client.assets.list_(name_regex="model-v[0-9]+") - -# Filter by creation date -assets = client.assets.list_( - created_after=datetime(2023, 1, 1), - created_before=datetime(2023, 12, 31) -) - -# Filter by modification date -assets = client.assets.list_( - modified_after=datetime(2023, 6, 1) +config = SiftConnectionConfig( + grpc_config=GrpcConfig( + uri="grpc.siftstack.com:443", + api_key="your-api-key", + use_ssl=True + ), + rest_config=RestConfig( + uri="https://api.siftstack.com", + api_key="your-api-key" + ) ) -# Include archived assets -all_assets = client.assets.list_(include_archived=True) - -# Limit the number of results -recent_assets = client.assets.list_( - limit=10, - order_by="modified_date desc" -) +client = SiftClient(connection_config=config) ``` +## Best Practices +1. **Use sync APIs** for notebooks, scripts, and simple applications +2. **Use async APIs** for high-performance services with concurrent operations +3. **Leverage filtering** to reduce data transfer and improve performance +4. **Reuse client instances** rather than creating new ones for each operation +5. **Use type hints** to get full IDE support and catch errors early """ import logging diff --git a/python/lib/sift_client/examples/__init__.py b/python/lib/sift_client/examples/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python/lib/sift_client/examples/generic_workflow_example.py b/python/lib/sift_client/examples/generic_workflow_example.py deleted file mode 100644 index 1263928ad..000000000 --- a/python/lib/sift_client/examples/generic_workflow_example.py +++ /dev/null @@ -1,127 +0,0 @@ -# import asyncio -# import os -# from datetime import datetime, timezone -# -# from sift_client.client import SiftClient -# -# # Import sift_client types for calculated channels and rules -# from sift_client.sift_types import ( -# CalculatedChannelUpdate, -# ChannelReference, -# RuleAction, -# RuleAnnotationType, -# RuleCreate, -# RuleUpdate, -# ) -# -# """ -# Placeholder for future examples. FD-67 -# """ -# -# -# async def main(): -# grpc_url = os.getenv("SIFT_GRPC_URI", "localhost:50051") -# api_key = os.getenv("SIFT_API_KEY", "") -# rest_url = os.getenv("SIFT_REST_URI", "localhost:8080") -# client = SiftClient(grpc_url=grpc_url, api_key=api_key, rest_url=rest_url) -# -# asset = client.assets.find(name="NostromoLV426") -# asset_id = asset.id_ -# print("Found asset", asset.name) -# -# calculated_channels = client.calculated_channels.list_( -# name_regex="velocity_per.*", -# asset_id=asset_id, -# ) -# updated = False -# calculated_channel = None -# if calculated_channels: -# print(f"Found calculated channels: {[cc.name for cc in calculated_channels]}") -# for cc in calculated_channels: -# if cc.name == "velocity_per_voltage": -# calculated_channel = cc.update( -# CalculatedChannelUpdate( -# expression="$1 / $2 + 0.1", -# expression_channel_references=cc.channel_references, -# ) -# ) -# print("Updated calculated channel", calculated_channel) -# else: -# # Create a calculated channel that divides mainmotor.velocity by voltage -# print("\nCreating calculated channel...") -# calculated_channel = client.calculated_channels.create( -# dict( -# name="velocity_per_voltage", -# description="Ratio of mainmotor velocity to voltage", -# expression="$1 / $2", # $1 = mainmotor.velocity, $2 = voltage -# channel_references=[ -# ChannelReference( -# channel_reference="$1", channel_identifier="mainmotor.velocity" -# ), -# ChannelReference(channel_reference="$2", channel_identifier="voltage"), -# ], -# units="velocity/voltage", -# asset_ids=[asset_id], -# user_notes="Created to monitor velocity-to-voltage ratio", -# ) -# ) -# print( -# f"Created calculated channel: {calculated_channel.name} (ID: {calculated_channel.calculated_channel_id})" -# ) -# -# # Create a rule that creates an annotation when the ratio is above 0.1 -# rule_search = "high_velocity_voltage" -# print(f"Looking for rule containing {rule_search}") -# rules = client.rules.list( -# name_contains=rule_search, -# ) -# if rules: -# print(f"Found rules: {[rule.name for rule in rules]}") -# # Example of batch get if you just had the rule ids: -# rules = client.rules.batch_get(rule_ids=[rule.rule_id for rule in rules]) -# print(f"Batch get on IDs also works: {[rule.name for rule in rules]}") -# -# rule = rules[0] -# print(f"Updating rule: {rule.name}") -# rule = rule.update( -# RuleUpdate( -# description=f"Alert when velocity-to-voltage ratio exceeds 0.1 (Updated at {datetime.now(tz=timezone.utc).isoformat()})", -# asset_ids=[asset_id], -# ) -# ) -# updated = True -# else: -# print(f"No rules found for {rule_search}") -# rules = client.rules.list_( -# asset_ids=[asset_id], -# ) -# if rules: -# print(f"However these rules do exist: {[rule.name for rule in rules]}") -# print("Attempting to create rule for high_velocity_voltage_ratio_alert") -# rule = client.rules.create( -# RuleCreate( -# name="high_velocity_voltage_ratio_alert", -# description="Alert when velocity-to-voltage ratio exceeds 0.1", -# expression="$1 > 0.1", -# channel_references=[ -# ChannelReference( -# channel_reference="$1", -# channel_identifier=calculated_channel.name, -# ), -# ], -# action=RuleAction.annotation( -# annotation_type=RuleAnnotationType.DATA_REVIEW, -# tags=["high_ratio", "alert"], -# default_assignee_user_id=None, # You can set a user ID here if needed -# ), -# ) -# ) -# print(f"Created rule: {rule.name} (ID: {rule.rule_id})") -# -# if updated: -# print("Second run through, deleting rule") -# rule.delete() -# -# -# if __name__ == "__main__": -# asyncio.run(main()) diff --git a/python/lib/sift_client/resources/__init__.py b/python/lib/sift_client/resources/__init__.py index 5997acb02..e4b7e2747 100644 --- a/python/lib/sift_client/resources/__init__.py +++ b/python/lib/sift_client/resources/__init__.py @@ -1,3 +1,155 @@ +"""Sift Resources - API interfaces for interacting with Sift services. + +This module provides high-level API interfaces for interacting with Sift resources. +Each resource API provides methods for common operations like listing, getting, creating, +updating, and archiving resources. + +## Overview + +Resource APIs are the primary way to interact with Sift services. They provide: + +- **Type-safe methods** with full IDE autocomplete support +- **Automatic type conversion** between protobuf and Python types +- **Flexible filtering** using CEL (Common Expression Language) queries +- **Both sync and async** versions of most APIs +- **Consistent interface** across all resource types + +## Synchronous vs Asynchronous APIs + +All resource APIs are available in both synchronous and asynchronous versions: + +- **Synchronous APIs** (e.g., `AssetsAPI`) - Ideal for scripts, notebooks, and simple applications +- **Asynchronous APIs** (e.g., `AssetsAPIAsync`) - Ideal for high-performance applications with concurrent operations + +### Example Usage + +```python +from sift_client import SiftClient + +client = SiftClient(api_key="...", grpc_url="...", rest_url="...") + +# Synchronous API usage +asset = client.assets.get(asset_id="asset123") +runs = client.runs.list_(assets=[asset.id_], limit=10) + +# Asynchronous API usage +async def get_data(): + asset = await client.async_.assets.get(asset_id="asset123") + runs = await client.async_.runs.list_(assets=[asset.id_], limit=10) + return asset, runs +``` + +## Common Methods + +Most resource APIs provide a consistent set of methods: + +### Query Methods + +- `get()` - Retrieve a single resource by ID or unique identifier +- `list_()` - Retrieve multiple resources with optional filtering +- `find()` - Find a single resource matching criteria (raises error if multiple found) + +### Modification Methods + +- `create()` - Create a new resource (where applicable) +- `update()` - Update an existing resource +- `archive()` - Archive a resource (soft delete) +- `unarchive()` - Restore an archived resource + +## Filtering and Querying + +Resource APIs support powerful filtering capabilities: + +### Common Filters + +- **Name filters**: `name`, `name_contains`, `name_regex` +- **Time filters**: `created_after`, `created_before`, `modified_after`, `modified_before` +- **User filters**: `created_by`, `modified_by` +- **Metadata filters**: `tags`, `metadata` +- **Archive filters**: `include_archived` + +### Resource-Specific Filters + +Each resource API may have additional filters: + +- **Runs**: `start_time_after`, `duration_greater_than`, `is_stopped` +- **Channels**: `asset`, `run` +- **Rules**: `asset_ids`, `asset_tag_ids` + +### Example: Advanced Filtering + +```python +from datetime import datetime, timedelta + +# Complex run query +runs = client.runs.list_( + assets=["asset123"], + start_time_after=datetime.now() - timedelta(days=7), + duration_greater_than=timedelta(hours=1), + is_stopped=True, + tags=["production"], + order_by="start_time desc", + limit=20 +) + +# Channel search with regex +channels = client.channels.list_( + asset="asset123", + name_regex="sensor_[0-9]+_temp", + limit=100 +) +``` + +## Data Retrieval + +The `ChannelsAPI` provides methods for retrieving time-series data: + +```python +from datetime import datetime, timedelta + +# Get channel data as pandas DataFrames +channels = client.channels.list_(asset="asset123", limit=5) +data = client.channels.get_data( + channels=channels, + run="run123", + start_time=datetime.now() - timedelta(hours=1), + end_time=datetime.now(), + limit=10000 +) + +# data is a dict mapping channel names to DataFrames +for channel_name, df in data.items(): + print(f"{channel_name}: {len(df)} data points") + print(df.head()) +``` + +## Async Context Usage + +When using async APIs, ensure proper async context: + +```python +import asyncio +from sift_client import SiftClient + +async def main(): + client = SiftClient(api_key="...", grpc_url="...", rest_url="...") + + # Use async_ accessor for async APIs + assets = await client.async_.assets.list_(limit=10) + + # Concurrent operations + asset_task = client.async_.assets.get(asset_id="asset123") + runs_task = client.async_.runs.list_(limit=10) + + asset, runs = await asyncio.gather(asset_task, runs_task) + + return asset, runs + +# Run the async function +result = asyncio.run(main()) +``` +""" + from sift_client.resources.assets import AssetsAPIAsync from sift_client.resources.calculated_channels import CalculatedChannelsAPIAsync from sift_client.resources.channels import ChannelsAPIAsync diff --git a/python/lib/sift_client/sift_types/__init__.py b/python/lib/sift_client/sift_types/__init__.py index 1af4eb80d..ad96d4db5 100644 --- a/python/lib/sift_client/sift_types/__init__.py +++ b/python/lib/sift_client/sift_types/__init__.py @@ -1,3 +1,134 @@ +"""Sift Types - Pydantic models for Sift resources. + +This module provides strongly-typed Pydantic models for interacting with Sift resources. +These models are used throughout the Sift client to provide type safety, validation, +and convenient methods for working with Sift objects. + +## Resource BaseTypes + +Resource BaseTypes are immutable Pydantic models that represent specific Sift objects +retrieved from the API. They provide: + +- **Type-safe access** to all resource properties +- **Convenience methods** for common operations (update, archive, etc.) +- **Related resource access** via properties (e.g., `asset.runs`, `run.assets`) +- **Rich integration** with IDEs for autocomplete and type checking + +### Available Resource Types + +- `Asset` - Physical or logical entities (vehicles, machines, devices) +- `Run` - Time-bounded operational periods for an asset +- `Channel` - Time-series data streams (sensor readings, telemetry) +- etc. + + +## Create and Update Types + +Create and Update types are Pydantic models used to create new resources or modify +existing ones. They can be used directly as typed objects or as dictionaries for +convenience. + +### Create Types + +Create types define the required and optional fields for creating new resources. For example: + +- `RunCreate` - Create a new run +- `CalculatedChannelCreate` - Create a new calculated channel +- `RuleCreate` - Create a new rule +- etc. + +### Update Types + +Update types define which fields can be modified on existing resources. For example: + +- `AssetUpdate` - Update asset properties +- `RunUpdate` - Update run properties +- `CalculatedChannelUpdate` - Update calculated channel properties +- etc. + +### Example Usage + +```python +from sift_client import SiftClient +from sift_client.sift_types import RunCreate, AssetUpdate + +client = SiftClient(api_key="...", grpc_url="...", rest_url="...") + +# Using Create types - typed approach +run = client.runs.create( + RunCreate( + name="Test Run", + description="A test run", + asset_ids=["asset123"], + start_time=datetime.now(), + ) +) + +# Using Create types - dict approach (more convenient) +run = client.runs.create({ + "name": "Test Run", + "description": "A test run", + "asset_ids": ["asset123"], + "start_time": datetime.now(), +}) + +# Using Update types - typed approach +asset = client.assets.update( + asset="asset123", + update=AssetUpdate(tags=["production", "v2"]) +) + +# Using Update types - dict approach (more convenient) +asset = client.assets.update( + asset="asset123", + update={"tags": ["production", "v2"]} +) + +# Using convenience methods on resource instances +asset.update({"tags": ["production", "v3"]}) +``` + +## Helper Types + +Additional types are provided for specific use cases. For example: + +- `ChannelReference` - Reference to a channel in expressions +- `ChannelDataType` - Enum for channel data types +- `ChannelBitFieldElement` - Bit field element definition +- `RuleActionType` - Enum for rule action types +- `RuleAnnotationType` - Enum for annotation types +- `ChannelConfig` - Configuration for data ingestion channels +- `Flow` - Data flow configuration for ingestion +- `IngestionConfig` - Complete ingestion configuration +- etc. + +## Type Validation + +All types use Pydantic for validation, ensuring: + +- **Required fields** are present +- **Field types** are correct +- **Datetime fields** have timezone information +- **Enum values** are valid + +Validation errors are raised immediately with clear error messages. + +## Immutability + +Resource BaseTypes (Asset, Run, etc.) are immutable by default. To update a resource, +use the `update()` method, which will update the instance in-place by replacing its +internal state with the updated values from the API. + +```python +asset = client.assets.get(asset_id="asset123") +# This will raise an error - assets are immutable +# asset.name = "New Name" + +# Instead, use the update method +asset.update({"tags": ["new-tag"]}) # Updates the instance in-place +``` +""" + from sift_client.sift_types.asset import Asset, AssetUpdate from sift_client.sift_types.calculated_channel import ( CalculatedChannel, diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 1644828f4..3c68c3564 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -53,10 +53,10 @@ extra: nav: - Home: index.md - - Examples: - - examples/sift_client.ipynb - Sift Py API - Sift Client API (New) + - Examples: + - examples/basic.ipynb # - Guides: # - Logging # - Error Handling @@ -68,6 +68,7 @@ plugins: deploy_prefix: 'python' # In case we want to use doc sites for other client libs too - mkdocs-jupyter: include_source: True + execute: False - mkdocstrings: default_handler: python handlers: