From de5583970a93d0ccab1dd7276fa1d1780d8b3d37 Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Wed, 21 Oct 2026 17:29:35 -0700 Subject: [PATCH 1/7] Remove get_sheet_data call. --- cirro/services/sheet.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cirro/services/sheet.py b/cirro/services/sheet.py index 774f12c..4e2e2aa 100644 --- a/cirro/services/sheet.py +++ b/cirro/services/sheet.py @@ -1,12 +1,12 @@ from typing import List from cirro_api_client.v1.api.sheets import get_sheets, get_sheet, create_sheet, update_sheet, delete_sheet, \ - get_sheet_data, query_sheet_data, query_data, insert_sheet_data, update_sheet_data, delete_sheet_data, \ + query_sheet_data, query_data, insert_sheet_data, update_sheet_data, delete_sheet_data, \ refresh_view, trigger_ingest, get_jobs from cirro_api_client.v1.models import Sheet, SheetDetail, TableSheetInput, ViewSheetInput, CreateResponse, \ SheetUpdateResponse, SheetDataRequest, SheetQueryRequest, SheetQueryResponse, SheetDataUpdateResponse, \ RowInsert, RowUpdate, InsertRowsRequest, UpdateRowsRequest, DeleteRowsRequest, SheetIngestRequest, SheetJob, \ - SqlSortOrder + SheetSort, SqlSortOrder from cirro_api_client.v1.types import UNSET from cirro.services.base import BaseService @@ -103,13 +103,12 @@ def get_data(self, order_by (str): Column to sort by order (SqlSortOrder): Sort direction """ - return get_sheet_data.sync(project_id=project_id, - sheet_id=sheet_id, - limit=limit, - page=page, - order_by=order_by if order_by is not None else UNSET, - order=order if order is not None else UNSET, - client=self._api_client) + sort = SheetSort(order_by=order_by if order_by is not None else UNSET, + order=order if order is not None else UNSET) + return query_sheet_data.sync(project_id=project_id, + sheet_id=sheet_id, + body=SheetDataRequest(limit=limit, page=page, sort=sort), + client=self._api_client) def query_data(self, project_id: str, sheet_id: str, request: SheetDataRequest) -> SheetQueryResponse: """ From d3b7d1aa9b4da645f4a3e4658cb01c40e44581be Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Thu, 22 Oct 2026 07:22:29 -0700 Subject: [PATCH 2/7] Remove deprecated method. --- cirro/sdk/sheet.py | 14 +++++++------- cirro/services/sheet.py | 38 ++++++-------------------------------- tests/test_sheets.py | 18 +++++++++--------- 3 files changed, 22 insertions(+), 48 deletions(-) diff --git a/cirro/sdk/sheet.py b/cirro/sdk/sheet.py index 6ed5c47..7ab6e77 100644 --- a/cirro/sdk/sheet.py +++ b/cirro/sdk/sheet.py @@ -4,8 +4,9 @@ from pandas import DataFrame from cirro_api_client.v1.models import ColumnDef, RowInsert, RowInsertValues, RowUpdate, RowUpdateValues, Sheet, \ - SheetDetail, SheetJob, SheetQueryResponse, SheetType, SqlSortOrder, Status, TableSheetInput, Tag, ViewSheetInput -from cirro_api_client.v1.types import Unset + SheetDataRequest, SheetDetail, SheetJob, SheetQueryResponse, SheetSort, SheetType, SqlSortOrder, Status, \ + TableSheetInput, Tag, ViewSheetInput +from cirro_api_client.v1.types import UNSET, Unset from cirro.cirro_client import CirroApi from cirro.sdk.asset import DataPortalAssets, DataPortalAsset @@ -168,14 +169,13 @@ def to_dataframe(self, Returns: `pandas.DataFrame` """ + sort = SheetSort(order_by=order_by if order_by is not None else UNSET, + order=order if order is not None else UNSET) return query_to_dataframe( - lambda limit, page: self._client.sheets.get_data( + lambda limit, page: self._client.sheets.query_data( project_id=self.project_id, sheet_id=self.id, - limit=limit, - page=page, - order_by=order_by, - order=order + request=SheetDataRequest(limit=limit, page=page, sort=sort) ), max_rows=max_rows ) diff --git a/cirro/services/sheet.py b/cirro/services/sheet.py index 4e2e2aa..58d7013 100644 --- a/cirro/services/sheet.py +++ b/cirro/services/sheet.py @@ -5,9 +5,7 @@ refresh_view, trigger_ingest, get_jobs from cirro_api_client.v1.models import Sheet, SheetDetail, TableSheetInput, ViewSheetInput, CreateResponse, \ SheetUpdateResponse, SheetDataRequest, SheetQueryRequest, SheetQueryResponse, SheetDataUpdateResponse, \ - RowInsert, RowUpdate, InsertRowsRequest, UpdateRowsRequest, DeleteRowsRequest, SheetIngestRequest, SheetJob, \ - SheetSort, SqlSortOrder -from cirro_api_client.v1.types import UNSET + RowInsert, RowUpdate, InsertRowsRequest, UpdateRowsRequest, DeleteRowsRequest, SheetIngestRequest, SheetJob from cirro.services.base import BaseService @@ -83,45 +81,21 @@ def delete(self, project_id: str, sheet_id: str) -> None: """ delete_sheet.sync_detailed(project_id=project_id, sheet_id=sheet_id, client=self._api_client) - def get_data(self, - project_id: str, - sheet_id: str, - limit: int = 1000, - page: int = 1, - order_by: str = None, - order: SqlSortOrder = None) -> SheetQueryResponse: + def query_data(self, project_id: str, sheet_id: str, request: SheetDataRequest = None) -> SheetQueryResponse: """ - Retrieve a page of rows from a sheet + Retrieve a page of rows from a sheet, with an optional sort and filter The first column is always `_row_id`, which identifies the row for `update_rows` and `delete_rows`. Args: project_id (str): ID of the Project sheet_id (str): ID of the Sheet - limit (int): Maximum number of rows to return (default 1,000) - page (int): Page to return (default 1) - order_by (str): Column to sort by - order (SqlSortOrder): Sort direction + request (cirro_api_client.v1.models.SheetDataRequest): Page, sort, and filter to apply; + defaults to the first 1,000 rows, unsorted and unfiltered """ - sort = SheetSort(order_by=order_by if order_by is not None else UNSET, - order=order if order is not None else UNSET) return query_sheet_data.sync(project_id=project_id, sheet_id=sheet_id, - body=SheetDataRequest(limit=limit, page=page, sort=sort), - client=self._api_client) - - def query_data(self, project_id: str, sheet_id: str, request: SheetDataRequest) -> SheetQueryResponse: - """ - Retrieve a page of rows from a sheet, with an optional sort and filter - - Args: - project_id (str): ID of the Project - sheet_id (str): ID of the Sheet - request (cirro_api_client.v1.models.SheetDataRequest): Page, sort, and filter to apply - """ - return query_sheet_data.sync(project_id=project_id, - sheet_id=sheet_id, - body=request, + body=request if request is not None else SheetDataRequest(), client=self._api_client) def raw_query(self, diff --git a/tests/test_sheets.py b/tests/test_sheets.py index d5a3021..ac756c1 100644 --- a/tests/test_sheets.py +++ b/tests/test_sheets.py @@ -52,7 +52,7 @@ def _make_sheet(client: Mock, **overrides) -> DataPortalSheet: def _paging_client(total_rows: int): """ - Mock client whose sheets.get_data pages the way the API does, reading the + Mock client whose sheets.query_data pages the way the API does, reading the slice at offset (page - 1) * limit. Returns the client alongside the list of (page, limit) pairs it was asked @@ -60,14 +60,14 @@ def _paging_client(total_rows: int): """ requests = [] - def get_data(project_id, sheet_id, limit, page, order_by, order): - requests.append((page, limit)) - start = (page - 1) * limit - rows = [[i, f"code-{i}"] for i in range(start, min(start + limit, total_rows))] + def query_data(project_id, sheet_id, request): + requests.append((request.page, request.limit)) + start = (request.page - 1) * request.limit + rows = [[i, f"code-{i}"] for i in range(start, min(start + request.limit, total_rows))] return SheetQueryResponse(columns=_COLUMNS, rows=rows, total_row_count=total_rows) client = Mock() - client.sheets.get_data.side_effect = get_data + client.sheets.query_data.side_effect = query_data return client, requests @@ -141,9 +141,9 @@ def test_sort_is_passed_through(self): client, _ = _paging_client(10) _make_sheet(client).to_dataframe(order_by="icd_code", order=SqlSortOrder.DESC) - _, kwargs = client.sheets.get_data.call_args - self.assertEqual("icd_code", kwargs["order_by"]) - self.assertEqual(SqlSortOrder.DESC, kwargs["order"]) + _, kwargs = client.sheets.query_data.call_args + self.assertEqual("icd_code", kwargs["request"].sort.order_by) + self.assertEqual(SqlSortOrder.DESC, kwargs["request"].sort.order) class NamespaceQueryTest(unittest.TestCase): From f040eae7fcbe29f287811359196571c766b8518b Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Thu, 22 Oct 2026 07:54:35 -0700 Subject: [PATCH 3/7] Bump to 1.8.0 and fix tests. --- cirro/sdk/sheet.py | 1 - poetry.lock | 8 ++++---- pyproject.toml | 2 +- tests/test_sheets.py | 9 ++++----- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/cirro/sdk/sheet.py b/cirro/sdk/sheet.py index 7ab6e77..36d04c0 100644 --- a/cirro/sdk/sheet.py +++ b/cirro/sdk/sheet.py @@ -281,7 +281,6 @@ def update(self, "namespace_name": detail.namespace_name, "table_name": detail.table_name, "description": self.description if description is None else description, - "audit_read_access": detail.audit_read_access, "tags": detail.tags if tags is None else tags } diff --git a/poetry.lock b/poetry.lock index 7f527ee..3d7a380 100644 --- a/poetry.lock +++ b/poetry.lock @@ -737,14 +737,14 @@ files = [ [[package]] name = "cirro-api-client" -version = "1.7.2" +version = "1.8.0" description = "A client library for accessing Cirro" optional = false python-versions = "<4.0,>=3.11" groups = ["main"] files = [ - {file = "cirro_api_client-1.7.2-py3-none-any.whl", hash = "sha256:a87dd1fdc0bfe11b5214b0b9a11cc14c93a889a0b173cf10ee2d046296b5c275"}, - {file = "cirro_api_client-1.7.2.tar.gz", hash = "sha256:7633f86a4e22beeef49ac4aad67b65a328918726ddda40f9a296f0c6c3da2aca"}, + {file = "cirro_api_client-1.8.0-py3-none-any.whl", hash = "sha256:8c214ee7a3222d1ce9131d6382925619f7f447b421efa2b1e7527f2955eeb9fb"}, + {file = "cirro_api_client-1.8.0.tar.gz", hash = "sha256:9e7b3b48b1071f7b6d9bbf962faabfc6aff9d5ee9af6699b4b790e6d7bd1b017"}, ] [package.dependencies] @@ -4602,4 +4602,4 @@ wdl = ["miniwdl"] [metadata] lock-version = "2.1" python-versions = ">3.11.0,<4.0" -content-hash = "3811a13427a5af94fab8b5f3f99fbbd106a22e7585d43b348b72ea22bf4c10c9" +content-hash = "1e9e4c155dc3d0e8b6250934ce71044e606ca0e3b8c5f4fa798e2a56251bad59" diff --git a/pyproject.toml b/pyproject.toml index 92a5e74..c2b7c19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ packages = [{include = "cirro"}] [tool.poetry.dependencies] python = ">3.11.0,<4.0" attrs = ">=21.3.0" -cirro_api_client = "~1.7.2" +cirro_api_client = "~1.8.0" click = "^8.5.0" boto3 = "~=1.43" questionary = "^2.0.1" diff --git a/tests/test_sheets.py b/tests/test_sheets.py index ac756c1..ee5bbb7 100644 --- a/tests/test_sheets.py +++ b/tests/test_sheets.py @@ -36,7 +36,8 @@ "updatedAt": "2026-01-01T00:00:00Z", "totalRowCount": 0, "tags": [], - "columnRelationships": [] + "columnRelationships": [], + "mappingTargets": [] } _COLUMNS = [ @@ -192,7 +193,7 @@ class CreateSheetTest(unittest.TestCase): def test_create_sheet_returns_the_new_sheet(self): client = Mock() client.sheets.create.return_value = CreateResponse(id="sheet-1", message="created") - client.sheets.get.return_value = SheetDetail.from_dict({**_SHEET, "auditReadAccess": False}) + client.sheets.get.return_value = SheetDetail.from_dict(_SHEET) project = DataPortalProject(Project.from_dict(_PROJECT), client) sheet_input = TableSheetInput( @@ -258,7 +259,6 @@ def _sheet_with_detail(**detail_overrides): client = Mock() client.sheets.get.return_value = SheetDetail.from_dict({ **_SHEET, - "auditReadAccess": False, "columns": [ColumnDef(name="icd_code", data_type=ColumnDataType.STRING).to_dict()], **detail_overrides }) @@ -294,7 +294,7 @@ def test_update_sends_the_whole_view_state(self): def test_update_reflects_the_rename_locally(self): sheet, client = self._sheet_with_detail() client.sheets.update.return_value = SheetUpdateResponse( - sheet=SheetDetail.from_dict({**_SHEET, "name": "Renamed", "auditReadAccess": False}) + sheet=SheetDetail.from_dict({**_SHEET, "name": "Renamed"}) ) sheet.update(name="Renamed") @@ -336,7 +336,6 @@ def test_columns_fetches_the_sheet_detail(self): client = Mock() client.sheets.get.return_value = SheetDetail.from_dict({ **_SHEET, - "auditReadAccess": False, "columns": [ColumnDef(name="icd_code", data_type=ColumnDataType.STRING).to_dict()] }) sheet = _make_sheet(client) From d87f2e501bcd4eb5578fb92af65733a6057bc85d Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Thu, 22 Oct 2026 07:56:00 -0700 Subject: [PATCH 4/7] Bump minor version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c2b7c19..62b4332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cirro" -version = "1.15.1" +version = "1.16.0" description = "CLI tool and SDK for interacting with the Cirro platform" authors = ["Cirro Bio "] license = "MIT" From 05cbead8fa9f3a08304ed6142c5f956498a3bee7 Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Fri, 23 Oct 2026 13:22:49 -0700 Subject: [PATCH 5/7] Add in samples. --- cirro/sdk/project.py | 1 + cirro/sdk/sheet.py | 1 + samples/Creating_a_sheet.ipynb | 737 +++++++++++++++++++++++++++++ samples/Inserting_sheet_rows.ipynb | 117 +++++ samples/Querying_sheets.ipynb | 331 +++++++++++++ samples/Reading_a_sheet.ipynb | 119 +++++ samples/Updating_sheet_rows.ipynb | 152 ++++++ tests/test_sheets.py | 3 + 8 files changed, 1461 insertions(+) create mode 100644 samples/Creating_a_sheet.ipynb create mode 100644 samples/Inserting_sheet_rows.ipynb create mode 100644 samples/Querying_sheets.ipynb create mode 100644 samples/Reading_a_sheet.ipynb create mode 100644 samples/Updating_sheet_rows.ipynb diff --git a/cirro/sdk/project.py b/cirro/sdk/project.py index 3c3b1c2..458bde0 100644 --- a/cirro/sdk/project.py +++ b/cirro/sdk/project.py @@ -288,6 +288,7 @@ def create_sheet(self, sheet: Union[TableSheetInput, ViewSheetInput]) -> DataPor diagnoses = project.create_sheet( TableSheetInput( + sheet_type="TABLE", name="Diagnoses", namespace_name="default", table_name="diagnoses", diff --git a/cirro/sdk/sheet.py b/cirro/sdk/sheet.py index 36d04c0..4bb86c4 100644 --- a/cirro/sdk/sheet.py +++ b/cirro/sdk/sheet.py @@ -277,6 +277,7 @@ def update(self, """ detail = self._get_detail() shared = { + "sheet_type": detail.sheet_type.value, "name": self.name if name is None else name, "namespace_name": detail.namespace_name, "table_name": detail.table_name, diff --git a/samples/Creating_a_sheet.ipynb b/samples/Creating_a_sheet.ipynb new file mode 100644 index 0000000..2deaa5a --- /dev/null +++ b/samples/Creating_a_sheet.ipynb @@ -0,0 +1,737 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a957f569", + "metadata": {}, + "source": [ + "## Creating a sheet\n", + "\n", + "Sheets are tables of structured data held alongside a project. A `TABLE` sheet holds rows you write to it directly, while a `VIEW` sheet is materialized from a query over other sheets.\n", + "\n", + "This notebook creates the `Example Samples` table used by the other Sheets samples." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "35546c45", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test project 1\")" + ] + }, + { + "cell_type": "markdown", + "id": "f4be0f47", + "metadata": {}, + "source": [ + "#### Listing the sheets in a project" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "69631c64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: drug_strength\n", + "Id: f97cdbee-3dbd-4bae-9c7e-e3d24aebf5b6\n", + "Description: OMOP drug strength mappings\n", + "Sheet Type: TABLE\n", + "Total Row Count: 2966868\n", + "\n", + "---\n", + "\n", + "Name: funnel\n", + "Id: 58ee1e0e-eaa6-4e9e-84e9-02daa98c7b56\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: 206\n", + "\n", + "---\n", + "\n", + "Name: procedure_occurrence\n", + "Id: a3dcf054-5be3-4fb8-a5c3-66114b220b41\n", + "Description: 1k sythetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 17333\n", + "\n", + "---\n", + "\n", + "Name: drug_exposure\n", + "Id: 0d383b75-e6ab-4c0e-a1c2-ed2692908bec\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 29518\n", + "\n", + "---\n", + "\n", + "Name: Cirro Files\n", + "Id: system-file-11a0fa10-d108-4151-a244-05d64df96136\n", + "Description: A complete list of the files in this project. Managed by Cirro.\n", + "Sheet Type: TABLE\n", + "Total Row Count: 2930\n", + "\n", + "---\n", + "\n", + "Name: Cirro File Catalog\n", + "Id: system-file_catalog-11a0fa10-d108-4151-a244-05d64df96136\n", + "Description: Every file in this project joined to its dataset. Managed by Cirro.\n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: observation_period\n", + "Id: 44ea6ba7-56e8-4e95-a996-bfb10b2fd8ad\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 1126\n", + "\n", + "---\n", + "\n", + "Name: test-measurement\n", + "Id: 19195c23-bc60-4bcc-9bc5-789bfba8f076\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 338550\n", + "\n", + "---\n", + "\n", + "Name: Virtual View Check\n", + "Id: c5eef674-aab5-4954-a430-38295f7cfd4e\n", + "Description: None\n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test encounters\n", + "Id: c365b80e-5aa1-406c-a86d-1006a617303f\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 82696\n", + "\n", + "---\n", + "\n", + "Name: concept_synonym\n", + "Id: b2eb38da-d82f-45a2-b70b-0f74448ff55b\n", + "Description: OMOP concept synonyms\n", + "Sheet Type: TABLE\n", + "Total Row Count: 2790997\n", + "\n", + "---\n", + "\n", + "Name: test after latest deploy\n", + "Id: e8d0e048-eaab-4db4-9d9c-8c40256d6fde\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: LL_Observation\n", + "Id: 16bc3c24-faea-4974-8656-236370173f81\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 421167\n", + "\n", + "---\n", + "\n", + "Name: test-large-file\n", + "Id: 9d8ce695-aa85-42d6-b243-0bf8a5cb9eb1\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 82700\n", + "\n", + "---\n", + "\n", + "Name: Patients Testing\n", + "Id: f5a2373d-b643-4e06-a783-bae502d793a7\n", + "Description: Primary patients table\n", + "Sheet Type: TABLE\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test-view\n", + "Id: e4c49c28-35ef-45b7-b476-20650f626a09\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: gtky_demo\n", + "Id: ae71c08c-aae7-4db9-940d-8cb7fe3a360c\n", + "Description: Getting To Know You survey results\n", + "Sheet Type: TABLE\n", + "Total Row Count: 438\n", + "\n", + "---\n", + "\n", + "Name: test-column-order\n", + "Id: 18deb2a5-9fe3-4db4-8312-786298b3174d\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: Allergies\n", + "Id: e36477a7-39f8-4542-957f-9ee4c5aa6d6c\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 1956\n", + "\n", + "---\n", + "\n", + "Name: test-file-folder\n", + "Id: bb0010dd-99d8-4264-9801-7fc088f6f064\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 69\n", + "\n", + "---\n", + "\n", + "Name: observation\n", + "Id: b1eba9c7-e7e6-4178-905e-593ae168dddd\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 8518\n", + "\n", + "---\n", + "\n", + "Name: test_vocab\n", + "Id: d0874e31-e446-48a8-81d3-e827bf0e5512\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 27\n", + "\n", + "---\n", + "\n", + "Name: test 55\n", + "Id: fe3b5241-18a7-471f-ac9c-cdfdacf5b5c2\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: relationship\n", + "Id: a1ab5519-9557-4525-b187-49845770e682\n", + "Description: OMOP relationship definitions\n", + "Sheet Type: TABLE\n", + "Total Row Count: 722\n", + "\n", + "---\n", + "\n", + "Name: Cirro Datasets\n", + "Id: system-dataset-11a0fa10-d108-4151-a244-05d64df96136\n", + "Description: The datasets in this project. Managed by Cirro.\n", + "Sheet Type: TABLE\n", + "Total Row Count: 167\n", + "\n", + "---\n", + "\n", + "Name: Patient\n", + "Id: 182f469b-8899-4427-af2a-b117199d5c5b\n", + "Description: This is the patient table for the demo.\n", + "Sheet Type: TABLE\n", + "Total Row Count: 13857\n", + "\n", + "---\n", + "\n", + "Name: domain\n", + "Id: 3ad637fc-76aa-4452-8bb2-e8b9e5f04b39\n", + "Description: OMOP concept domains\n", + "Sheet Type: TABLE\n", + "Total Row Count: 50\n", + "\n", + "---\n", + "\n", + "Name: test view\n", + "Id: f3c8e037-d5ad-4789-899a-024d3e9d0fbc\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: 100\n", + "\n", + "---\n", + "\n", + "Name: test 3\n", + "Id: 2c0121aa-028f-4923-94cd-aea5e8a85d4b\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2\n", + "\n", + "---\n", + "\n", + "Name: test-manual-renamed\n", + "Id: 6b947f6e-dfcb-4df9-a2f5-9a9b6631753c\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: Allergies from parquet test\n", + "Id: 3c6d10b8-6e18-450a-a142-cbfd022703a2\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: test required int\n", + "Id: 46c79596-eb2e-47d5-8e1b-230643fa8431\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2\n", + "\n", + "---\n", + "\n", + "Name: file catalog view\n", + "Id: 5545fcf4-7bb5-4dc0-806b-61e2ac6681b3\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: Encounters\n", + "Id: 7ff8ee2a-7de5-4e6c-848a-80ba6a8bc189\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 248097\n", + "\n", + "---\n", + "\n", + "Name: drug_era\n", + "Id: 37c8bf91-c7e4-4283-9b75-868e315eb2b1\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 6652\n", + "\n", + "---\n", + "\n", + "Name: test rename\n", + "Id: bcacf8e8-8616-4042-803e-710e91462ad9\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: test-view-sql\n", + "Id: 55214476-57f5-424f-aa21-2ec518c75085\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test-view-existing\n", + "Id: a03a9fe8-8456-4bad-95a5-ef240f203dd1\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: nathan test\n", + "Id: 354620ea-82ed-4ae3-8bc9-89e31472c520\n", + "Description: test cirro file performance\n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: gtky_for_dashboard\n", + "Id: 1bb6a67c-45b2-48ac-b6b4-652ba87dbf07\n", + "Description: Denormalized data from the Getting To Know You survey\n", + "Sheet Type: TABLE\n", + "Total Row Count: 544\n", + "\n", + "---\n", + "\n", + "Name: test-kw-\n", + "Id: 9c500898-7dec-4309-b171-78434ee2c2e3\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: person\n", + "Id: 31b6f542-4ed0-4098-ad2e-6ef9b02142d7\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 1130\n", + "\n", + "---\n", + "\n", + "Name: visit_occurrence\n", + "Id: 4b8bacd5-ab92-4d28-b2a1-dd615c2066bf\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 32153\n", + "\n", + "---\n", + "\n", + "Name: test-required-field\n", + "Id: b39f20c0-cdbf-4d37-9eb5-f44f0d09fe23\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2\n", + "\n", + "---\n", + "\n", + "Name: Virtual Raw Check\n", + "Id: 9ac53e99-311d-4d2b-a7fc-1c0a850f2c10\n", + "Description: None\n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test\n", + "Id: ad80e579-6395-4c06-8890-a70b3f7b8b9e\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2\n", + "\n", + "---\n", + "\n", + "Name: test file\n", + "Id: 1ee4ab0a-445c-47d4-a900-aac0c5ae737a\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: Medications\n", + "Id: 828bc9e2-c04a-41a1-810d-5c4dcbca377d\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 76903\n", + "\n", + "---\n", + "\n", + "Name: concept\n", + "Id: cce071c6-c0d8-43b9-8f82-f13928f0770d\n", + "Description: Standard OMOP concept map with CPT4\n", + "Sheet Type: TABLE\n", + "Total Row Count: 6642023\n", + "\n", + "---\n", + "\n", + "Name: condition_era\n", + "Id: 982d963b-fe56-4ca0-9d57-79ea0d311675\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 7897\n", + "\n", + "---\n", + "\n", + "Name: DashboardCounts\n", + "Id: b2675672-53c6-4827-a82f-d5658e28661a\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 321\n", + "\n", + "---\n", + "\n", + "Name: test-file-project\n", + "Id: da60eaae-faaa-48b3-88d3-035c2ef90eb4\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: concept_relationship\n", + "Id: 9ac24ce7-a715-4a01-b54d-6826978e01e7\n", + "Description: OMOP concept relationship mapping\n", + "Sheet Type: TABLE\n", + "Total Row Count: 41672967\n", + "\n", + "---\n", + "\n", + "Name: Update display name mat view\n", + "Id: 54dc3697-0c96-40d7-b614-4152b3d90ff5\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: 980\n", + "\n", + "---\n", + "\n", + "Name: concept_ancestor\n", + "Id: fbc147a9-9122-4271-9195-4ffd584a6198\n", + "Description: OMOP concept ancestor mapping\n", + "Sheet Type: TABLE\n", + "Total Row Count: 79700074\n", + "\n", + "---\n", + "\n", + "Name: condition_occurrence\n", + "Id: fa53b18b-5278-428e-bb19-1d4b00113f8b\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 7900\n", + "\n", + "---\n", + "\n", + "Name: test_measurement_2\n", + "Id: 29b35c35-44d6-4e84-b6a5-2c4e7c64ffa3\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 338561\n", + "\n", + "---\n", + "\n", + "Name: test-view-aggregate\n", + "Id: 7e2f7eff-50d0-4bb3-8afb-0ca8aa4d1f97\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test json\n", + "Id: 7bb1f9a1-6ada-4ae8-b1c5-3bc3152a8c40\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2\n", + "\n", + "---\n", + "\n", + "Name: mdh_demo\n", + "Id: 9bfeff46-0aaa-4e56-b73e-2d8700b8240a\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 437\n", + "\n", + "---\n", + "\n", + "Name: Conditions\n", + "Id: d5859d3e-35a1-40ac-81f8-3e265093b7c0\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 42980\n", + "\n", + "---\n", + "\n", + "Name: test 2\n", + "Id: 5f7cb6fe-5f45-40d2-a681-f2e79b40106f\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: concept_class\n", + "Id: 34df6a38-8613-4f4f-ae03-31628a7dd274\n", + "Description: OMOP concept classes\n", + "Sheet Type: TABLE\n", + "Total Row Count: 433\n", + "\n", + "---\n", + "\n", + "Name: test wo kms\n", + "Id: a94b538c-ddac-40a9-91db-ec900fbc907a\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: 2024 patients\n", + "Id: 369c61eb-e2c6-4447-b378-1359eb1e12d8\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 1\n", + "\n", + "---\n", + "\n", + "Name: Allergies-test-move\n", + "Id: 2c5501a6-651c-4b9f-8ce6-f1b7c8af9329\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 976\n", + "\n", + "---\n", + "\n", + "Name: test-structured-view2\n", + "Id: ab4ad104-a1b8-44da-8839-9ac4f526a48f\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test_omop_ca\n", + "Id: 25230d41-3493-46d7-a71d-16bb9f750750\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: 78441023\n", + "\n", + "---\n", + "\n", + "Name: Allergies-test\n", + "Id: 44732f43-f51f-400e-a044-846edc28e045\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 2928\n", + "\n", + "---\n", + "\n", + "Name: test-if-see-arn\n", + "Id: a2019b2e-b7b8-44d0-995b-3962c67d7775\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n", + "\n", + "---\n", + "\n", + "Name: test-view-modal-updates\n", + "Id: 9b5fbefe-4f4d-44d8-8ac1-08875965233e\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: vocabulary\n", + "Id: 986ee515-2ac6-40d8-a2e9-c9058a8cfc14\n", + "Description: OMOP vocabularies\n", + "Sheet Type: TABLE\n", + "Total Row Count: 62\n", + "\n", + "---\n", + "\n", + "Name: test-structured-view\n", + "Id: 57a3af7c-2421-4ec2-9007-10a99128082c\n", + "Description: \n", + "Sheet Type: VIEW\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: foo\n", + "Id: 5f0479d2-f432-4231-a494-352f7c31b9b9\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: None\n", + "\n", + "---\n", + "\n", + "Name: test 22\n", + "Id: 7a100096-ba53-4c79-9b7a-1b2e2c4f3083\n", + "Description: \n", + "Sheet Type: TABLE\n", + "Total Row Count: 3\n", + "\n", + "---\n", + "\n", + "Name: measurement\n", + "Id: 4e4239a3-af89-4153-9484-6644779ea04a\n", + "Description: 1k synthetic records\n", + "Sheet Type: TABLE\n", + "Total Row Count: 199514\n" + ] + } + ], + "source": [ + "print(project.list_sheets().description())" + ] + }, + { + "cell_type": "markdown", + "id": "28dd94a5", + "metadata": {}, + "source": "#### Defining the columns\n\nEach column has a name and a data type. A `required` column cannot be null, and a `unique` column rejects duplicate values." + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4755a39c", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro_api_client.v1.models import ColumnDataType, ColumnDef\n", + "\n", + "columns = [\n", + " ColumnDef(name=\"sample_id\", data_type=ColumnDataType.STRING, required=True, unique=True),\n", + " ColumnDef(name=\"tissue\", data_type=ColumnDataType.STRING),\n", + " ColumnDef(name=\"condition\", data_type=ColumnDataType.STRING),\n", + " ColumnDef(name=\"read_count\", data_type=ColumnDataType.BIGINT)\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "cedbeda1", + "metadata": {}, + "source": "#### Creating the sheet\n\n`sheet_type` says whether the sheet is a `TABLE` or a `VIEW`. `name` is what the sheet is called in Cirro, and `table_name` is how SQL queries refer to it. `namespace_name` groups related tables and cannot be changed after the sheet is created." + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e29db72", + "metadata": {}, + "outputs": [], + "source": "from cirro_api_client.v1.models import SheetCreationMode, TableSheetInput\n\nsheet = project.create_sheet(\n TableSheetInput(\n sheet_type=\"TABLE\",\n name=\"Example Samples\",\n description=\"Sample metadata used by the Sheets examples\",\n namespace_name=\"default\",\n table_name=\"example_samples\",\n sheet_creation_mode=SheetCreationMode.STANDARD,\n columns=columns\n )\n)\nprint(sheet)" + }, + { + "cell_type": "markdown", + "id": "8aeacb51", + "metadata": {}, + "source": [ + "#### Deleting a sheet\n", + "\n", + "Once you are done with the examples, delete the sheet along with the rows it holds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "510771b4", + "metadata": {}, + "outputs": [], + "source": [ + "# sheet.delete()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "cirro-sdk", + "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.13.17" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/samples/Inserting_sheet_rows.ipynb b/samples/Inserting_sheet_rows.ipynb new file mode 100644 index 0000000..67d13f9 --- /dev/null +++ b/samples/Inserting_sheet_rows.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7f0d4b41", + "metadata": {}, + "source": "## Inserting rows into a sheet\n\nEach row is a mapping of column name to value, and columns left out of a row are null." + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fb8d91d", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test Project\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1c86c9d", + "metadata": {}, + "outputs": [], + "source": [ + "sheet = project.get_sheet_by_name(\"Example Samples\")" + ] + }, + { + "cell_type": "markdown", + "id": "8f7f1bd7", + "metadata": {}, + "source": [ + "#### Inserting a few rows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9099602a", + "metadata": {}, + "outputs": [], + "source": [ + "inserted = sheet.insert_rows([\n", + " {\"sample_id\": \"S1\", \"tissue\": \"liver\", \"condition\": \"control\", \"read_count\": 21000000},\n", + " {\"sample_id\": \"S2\", \"tissue\": \"liver\", \"condition\": \"treated\", \"read_count\": 19500000},\n", + " {\"sample_id\": \"S3\", \"tissue\": \"lung\", \"condition\": \"control\"}\n", + "])\n", + "print(f\"Inserted {inserted} rows\")" + ] + }, + { + "cell_type": "markdown", + "id": "bc4ce616", + "metadata": {}, + "source": [ + "#### Inserting rows from a DataFrame\n", + "\n", + "`to_dict(orient=\"records\")` turns each row of a DataFrame into the mapping `insert_rows` expects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fcc24bd", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.DataFrame({\n", + " \"sample_id\": [\"S4\", \"S5\"],\n", + " \"tissue\": [\"kidney\", \"lung\"],\n", + " \"condition\": [\"treated\", \"treated\"],\n", + " \"read_count\": [18000000, 22500000]\n", + "})\n", + "\n", + "inserted = sheet.insert_rows(df.to_dict(orient=\"records\"))\n", + "print(f\"Inserted {inserted} rows\")" + ] + }, + { + "cell_type": "markdown", + "id": "ebe88d48", + "metadata": {}, + "source": [ + "#### Checking the result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f8f11a0", + "metadata": {}, + "outputs": [], + "source": [ + "sheet.to_dataframe()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/samples/Querying_sheets.ipynb b/samples/Querying_sheets.ipynb new file mode 100644 index 0000000..6a4ea15 --- /dev/null +++ b/samples/Querying_sheets.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "16144707", + "metadata": {}, + "source": [ + "## Querying sheets with SQL\n", + "\n", + "`query_sheets` runs SQL across all the sheets in a project and returns the result as a DataFrame. Refer to a sheet by its `table_name` (here `example_samples`), not its display name." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d7795336", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test project 1\")" + ] + }, + { + "cell_type": "markdown", + "id": "5181cd98", + "metadata": {}, + "source": [ + "#### Filtering rows" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "621129df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sample_idconditionread_count
0S1control21000000
1S2treated19500000
\n", + "
" + ], + "text/plain": [ + " sample_id condition read_count\n", + "0 S1 control 21000000\n", + "1 S2 treated 19500000" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.query_sheets(\"\"\"\n", + " SELECT sample_id, condition, read_count\n", + " FROM example_samples\n", + " WHERE tissue = 'liver'\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "301f075e", + "metadata": {}, + "source": [ + "#### Summarizing rows" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "17110b90", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
conditionsamplesmean_reads
0treated320000000.0
1control221000000.0
\n", + "
" + ], + "text/plain": [ + " condition samples mean_reads\n", + "0 treated 3 20000000.0\n", + "1 control 2 21000000.0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.query_sheets(\"\"\"\n", + " SELECT condition, COUNT(*) AS samples, AVG(read_count) AS mean_reads\n", + " FROM example_samples\n", + " GROUP BY condition\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "ba7112f3", + "metadata": {}, + "source": [ + "#### Limiting a large result\n", + "\n", + "`query_sheets` reads the whole result unless `max_rows` stops it early." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "55630001", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
sample_idtissueconditionread_countsys_created_atsys_updated_at
0S1livercontrol210000002026-10-23T20:14:51.671965Z2026-10-23T20:14:51.671965Z
1S2livertreated195000002026-10-23T20:14:51.671965Z2026-10-23T20:14:51.671965Z
\n", + "
" + ], + "text/plain": [ + " sample_id tissue condition read_count sys_created_at \\\n", + "0 S1 liver control 21000000 2026-10-23T20:14:51.671965Z \n", + "1 S2 liver treated 19500000 2026-10-23T20:14:51.671965Z \n", + "\n", + " sys_updated_at \n", + "0 2026-10-23T20:14:51.671965Z \n", + "1 2026-10-23T20:14:51.671965Z " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "project.query_sheets(\"SELECT * FROM example_samples\", max_rows=2)" + ] + }, + { + "cell_type": "markdown", + "id": "e9fb71d1", + "metadata": {}, + "source": [ + "#### Querying from a Cirro workspace\n", + "\n", + "Inside a workspace with Sheets enabled, `cirro.helpers.workspace` queries the project's sheets directly with DuckDB, with no Cirro login. Refer to a sheet as `sheets..`. This needs the `sheets` extra (`pip install \"cirro[sheets]\"`), and the cell below only runs inside a workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07212bc7", + "metadata": {}, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "connect_sheets() only works inside a Cirro workspace with Sheets enabled. Elsewhere, use project.query_sheets().", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mRuntimeError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m cirro.helpers.workspace \u001b[38;5;28;01mimport\u001b[39;00m query_sheets\n\u001b[32m 2\u001b[39m \n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m query_sheets(\u001b[33m\"SELECT tissue, COUNT(*) AS samples FROM sheets.default.example_samples GROUP BY tissue\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/code/Cirro-sdk-python/cirro/helpers/workspace.py:71\u001b[39m, in \u001b[36mquery_sheets\u001b[39m\u001b[34m(sql)\u001b[39m\n\u001b[32m 57\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mquery_sheets\u001b[39m(sql: \u001b[38;5;28mstr\u001b[39m) -> \u001b[33m'\u001b[39m\u001b[33mDataFrame\u001b[39m\u001b[33m'\u001b[39m:\n\u001b[32m 58\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 59\u001b[39m \u001b[33;03m Run a query against the project's Sheets and return the result as a Pandas DataFrame.\u001b[39;00m\n\u001b[32m 60\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 69\u001b[39m \u001b[33;03m `pandas.DataFrame`\u001b[39;00m\n\u001b[32m 70\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m71\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[30;43mconnect_sheets\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m \u001b[38;5;28;01mas\u001b[39;00m conn:\n\u001b[32m 72\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m conn.sql(sql).df()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/code/Cirro-sdk-python/cirro/helpers/workspace.py:42\u001b[39m, in \u001b[36mconnect_sheets\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 40\u001b[39m \u001b[38;5;66;03m# Cirro sets this, with the project, account and region, on workspaces whose role can read Sheets\u001b[39;00m\n\u001b[32m 41\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m os.environ.get(\u001b[33m\"\u001b[39m\u001b[33mCIRRO_SHEETS_ENABLED\u001b[39m\u001b[33m\"\u001b[39m) != \u001b[33m\"\u001b[39m\u001b[33mtrue\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mconnect_sheets() only works inside a Cirro workspace with Sheets enabled. \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 43\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mElsewhere, use project.query_sheets().\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 44\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 45\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mduckdb\u001b[39;00m\n", + "\u001b[31mRuntimeError\u001b[39m: connect_sheets() only works inside a Cirro workspace with Sheets enabled. Elsewhere, use project.query_sheets()." + ] + } + ], + "source": [ + "# from cirro.helpers.workspace import query_sheets\n", + "\n", + "# query_sheets(\"SELECT tissue, COUNT(*) AS samples FROM sheets.default.example_samples GROUP BY tissue\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ea10db8f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "cirro-sdk", + "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.13.17" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/Reading_a_sheet.ipynb b/samples/Reading_a_sheet.ipynb new file mode 100644 index 0000000..a0a81c1 --- /dev/null +++ b/samples/Reading_a_sheet.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "32bcb218", + "metadata": {}, + "source": [ + "## Reading a sheet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8a363b7", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test Project\")" + ] + }, + { + "cell_type": "markdown", + "id": "1aedddd1", + "metadata": {}, + "source": [ + "#### Finding a sheet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1402af8", + "metadata": {}, + "outputs": [], + "source": [ + "sheet = project.get_sheet_by_name(\"Example Samples\")\n", + "print(sheet)" + ] + }, + { + "cell_type": "markdown", + "id": "23dbdb6f", + "metadata": {}, + "source": [ + "#### Inspecting the columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6dae1513", + "metadata": {}, + "outputs": [], + "source": [ + "for column in sheet.columns:\n", + " print(column.name, column.data_type)" + ] + }, + { + "cell_type": "markdown", + "id": "2458f42b", + "metadata": {}, + "source": [ + "#### Reading rows into a DataFrame\n", + "\n", + "`to_dataframe` pages through the whole sheet. The first column, `_row_id`, identifies each row for `update_rows` and `delete_rows`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4d2c5bb", + "metadata": {}, + "outputs": [], + "source": [ + "df = sheet.to_dataframe()\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "4554e32c", + "metadata": {}, + "source": [ + "#### Reading part of a sheet\n", + "\n", + "Sort the rows and stop after a set number of them. To filter rows, see `Querying_sheets.ipynb`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35e35b2c", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro_api_client.v1.models import SqlSortOrder\n", + "\n", + "sheet.to_dataframe(max_rows=3, order_by=\"read_count\", order=SqlSortOrder.DESC)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/Updating_sheet_rows.ipynb b/samples/Updating_sheet_rows.ipynb new file mode 100644 index 0000000..f45ff36 --- /dev/null +++ b/samples/Updating_sheet_rows.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4f106507", + "metadata": {}, + "source": [ + "## Updating rows in a sheet\n", + "\n", + "Rows are identified by `_row_id`, which `to_dataframe` returns as the first column. Read the sheet, find the rows to change, then pass their `_row_id`s to `update_rows` or `delete_rows`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "effbe5f9", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test Project\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a08cc9d6", + "metadata": {}, + "outputs": [], + "source": [ + "sheet = project.get_sheet_by_name(\"Example Samples\")" + ] + }, + { + "cell_type": "markdown", + "id": "9a864c9f", + "metadata": {}, + "source": [ + "#### Finding the rows to change" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f02ab92", + "metadata": {}, + "outputs": [], + "source": [ + "df = sheet.to_dataframe()\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "c3dad806", + "metadata": {}, + "source": [ + "#### Updating one row\n", + "\n", + "Only the columns you name are written; the rest of the row is left alone." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dc528020", + "metadata": {}, + "outputs": [], + "source": [ + "row_id = int(df.loc[df[\"sample_id\"] == \"S3\", \"_row_id\"].iloc[0])\n", + "\n", + "sheet.update_rows({row_id: {\"read_count\": 20100000}})" + ] + }, + { + "cell_type": "markdown", + "id": "853566de", + "metadata": {}, + "source": [ + "#### Updating several rows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2c51ced", + "metadata": {}, + "outputs": [], + "source": [ + "# Mark every lung sample as treated\n", + "lung = df.loc[df[\"tissue\"] == \"lung\", \"_row_id\"]\n", + "\n", + "updated = sheet.update_rows({int(row_id): {\"condition\": \"treated\"} for row_id in lung})\n", + "print(f\"Updated {updated} rows\")" + ] + }, + { + "cell_type": "markdown", + "id": "3a6b5e40", + "metadata": {}, + "source": [ + "#### Deleting rows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "343236ad", + "metadata": {}, + "outputs": [], + "source": [ + "to_delete = df.loc[df[\"sample_id\"] == \"S5\", \"_row_id\"]\n", + "\n", + "deleted = sheet.delete_rows([int(row_id) for row_id in to_delete])\n", + "print(f\"Deleted {deleted} rows\")" + ] + }, + { + "cell_type": "markdown", + "id": "ff161d41", + "metadata": {}, + "source": [ + "#### Checking the result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f8c93a5", + "metadata": {}, + "outputs": [], + "source": [ + "sheet.to_dataframe()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/test_sheets.py b/tests/test_sheets.py index ee5bbb7..5b87ecd 100644 --- a/tests/test_sheets.py +++ b/tests/test_sheets.py @@ -273,6 +273,8 @@ def test_update_sends_the_whole_table_state(self): _, kwargs = client.sheets.update.call_args target = kwargs["sheet"] self.assertIsInstance(target, TableSheetInput) + # the API tells a table from a view by sheetType, so it must be sent + self.assertEqual("TABLE", target.sheet_type) self.assertEqual("Renamed", target.name) # untouched fields are carried over rather than dropped self.assertEqual("ICD codes", target.description) @@ -288,6 +290,7 @@ def test_update_sends_the_whole_view_state(self): _, kwargs = client.sheets.update.call_args self.assertIsInstance(kwargs["sheet"], ViewSheetInput) + self.assertEqual("VIEW", kwargs["sheet"].sheet_type) self.assertEqual("Rolled up", kwargs["sheet"].description) self.assertEqual("Diagnoses", kwargs["sheet"].name) From 9cdb6a512024c3c726be94ae1ffc6793a9b39961 Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Fri, 23 Oct 2026 13:48:33 -0700 Subject: [PATCH 6/7] Review comments. --- samples/Creating_a_sheet.ipynb | 737 ----------------------------- samples/Inserting_sheet_rows.ipynb | 117 ----- samples/Querying_sheets.ipynb | 331 ------------- samples/Reading_a_sheet.ipynb | 119 ----- samples/Updating_sheet_rows.ipynb | 152 ------ samples/Working_with_sheets.ipynb | 215 +++++++++ 6 files changed, 215 insertions(+), 1456 deletions(-) delete mode 100644 samples/Creating_a_sheet.ipynb delete mode 100644 samples/Inserting_sheet_rows.ipynb delete mode 100644 samples/Querying_sheets.ipynb delete mode 100644 samples/Reading_a_sheet.ipynb delete mode 100644 samples/Updating_sheet_rows.ipynb create mode 100644 samples/Working_with_sheets.ipynb diff --git a/samples/Creating_a_sheet.ipynb b/samples/Creating_a_sheet.ipynb deleted file mode 100644 index 2deaa5a..0000000 --- a/samples/Creating_a_sheet.ipynb +++ /dev/null @@ -1,737 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a957f569", - "metadata": {}, - "source": [ - "## Creating a sheet\n", - "\n", - "Sheets are tables of structured data held alongside a project. A `TABLE` sheet holds rows you write to it directly, while a `VIEW` sheet is materialized from a query over other sheets.\n", - "\n", - "This notebook creates the `Example Samples` table used by the other Sheets samples." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "35546c45", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro import DataPortal\n", - "\n", - "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test project 1\")" - ] - }, - { - "cell_type": "markdown", - "id": "f4be0f47", - "metadata": {}, - "source": [ - "#### Listing the sheets in a project" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "69631c64", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Name: drug_strength\n", - "Id: f97cdbee-3dbd-4bae-9c7e-e3d24aebf5b6\n", - "Description: OMOP drug strength mappings\n", - "Sheet Type: TABLE\n", - "Total Row Count: 2966868\n", - "\n", - "---\n", - "\n", - "Name: funnel\n", - "Id: 58ee1e0e-eaa6-4e9e-84e9-02daa98c7b56\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: 206\n", - "\n", - "---\n", - "\n", - "Name: procedure_occurrence\n", - "Id: a3dcf054-5be3-4fb8-a5c3-66114b220b41\n", - "Description: 1k sythetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 17333\n", - "\n", - "---\n", - "\n", - "Name: drug_exposure\n", - "Id: 0d383b75-e6ab-4c0e-a1c2-ed2692908bec\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 29518\n", - "\n", - "---\n", - "\n", - "Name: Cirro Files\n", - "Id: system-file-11a0fa10-d108-4151-a244-05d64df96136\n", - "Description: A complete list of the files in this project. Managed by Cirro.\n", - "Sheet Type: TABLE\n", - "Total Row Count: 2930\n", - "\n", - "---\n", - "\n", - "Name: Cirro File Catalog\n", - "Id: system-file_catalog-11a0fa10-d108-4151-a244-05d64df96136\n", - "Description: Every file in this project joined to its dataset. Managed by Cirro.\n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: observation_period\n", - "Id: 44ea6ba7-56e8-4e95-a996-bfb10b2fd8ad\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 1126\n", - "\n", - "---\n", - "\n", - "Name: test-measurement\n", - "Id: 19195c23-bc60-4bcc-9bc5-789bfba8f076\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 338550\n", - "\n", - "---\n", - "\n", - "Name: Virtual View Check\n", - "Id: c5eef674-aab5-4954-a430-38295f7cfd4e\n", - "Description: None\n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test encounters\n", - "Id: c365b80e-5aa1-406c-a86d-1006a617303f\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 82696\n", - "\n", - "---\n", - "\n", - "Name: concept_synonym\n", - "Id: b2eb38da-d82f-45a2-b70b-0f74448ff55b\n", - "Description: OMOP concept synonyms\n", - "Sheet Type: TABLE\n", - "Total Row Count: 2790997\n", - "\n", - "---\n", - "\n", - "Name: test after latest deploy\n", - "Id: e8d0e048-eaab-4db4-9d9c-8c40256d6fde\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: LL_Observation\n", - "Id: 16bc3c24-faea-4974-8656-236370173f81\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 421167\n", - "\n", - "---\n", - "\n", - "Name: test-large-file\n", - "Id: 9d8ce695-aa85-42d6-b243-0bf8a5cb9eb1\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 82700\n", - "\n", - "---\n", - "\n", - "Name: Patients Testing\n", - "Id: f5a2373d-b643-4e06-a783-bae502d793a7\n", - "Description: Primary patients table\n", - "Sheet Type: TABLE\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test-view\n", - "Id: e4c49c28-35ef-45b7-b476-20650f626a09\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: gtky_demo\n", - "Id: ae71c08c-aae7-4db9-940d-8cb7fe3a360c\n", - "Description: Getting To Know You survey results\n", - "Sheet Type: TABLE\n", - "Total Row Count: 438\n", - "\n", - "---\n", - "\n", - "Name: test-column-order\n", - "Id: 18deb2a5-9fe3-4db4-8312-786298b3174d\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: Allergies\n", - "Id: e36477a7-39f8-4542-957f-9ee4c5aa6d6c\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 1956\n", - "\n", - "---\n", - "\n", - "Name: test-file-folder\n", - "Id: bb0010dd-99d8-4264-9801-7fc088f6f064\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 69\n", - "\n", - "---\n", - "\n", - "Name: observation\n", - "Id: b1eba9c7-e7e6-4178-905e-593ae168dddd\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 8518\n", - "\n", - "---\n", - "\n", - "Name: test_vocab\n", - "Id: d0874e31-e446-48a8-81d3-e827bf0e5512\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 27\n", - "\n", - "---\n", - "\n", - "Name: test 55\n", - "Id: fe3b5241-18a7-471f-ac9c-cdfdacf5b5c2\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: relationship\n", - "Id: a1ab5519-9557-4525-b187-49845770e682\n", - "Description: OMOP relationship definitions\n", - "Sheet Type: TABLE\n", - "Total Row Count: 722\n", - "\n", - "---\n", - "\n", - "Name: Cirro Datasets\n", - "Id: system-dataset-11a0fa10-d108-4151-a244-05d64df96136\n", - "Description: The datasets in this project. Managed by Cirro.\n", - "Sheet Type: TABLE\n", - "Total Row Count: 167\n", - "\n", - "---\n", - "\n", - "Name: Patient\n", - "Id: 182f469b-8899-4427-af2a-b117199d5c5b\n", - "Description: This is the patient table for the demo.\n", - "Sheet Type: TABLE\n", - "Total Row Count: 13857\n", - "\n", - "---\n", - "\n", - "Name: domain\n", - "Id: 3ad637fc-76aa-4452-8bb2-e8b9e5f04b39\n", - "Description: OMOP concept domains\n", - "Sheet Type: TABLE\n", - "Total Row Count: 50\n", - "\n", - "---\n", - "\n", - "Name: test view\n", - "Id: f3c8e037-d5ad-4789-899a-024d3e9d0fbc\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: 100\n", - "\n", - "---\n", - "\n", - "Name: test 3\n", - "Id: 2c0121aa-028f-4923-94cd-aea5e8a85d4b\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2\n", - "\n", - "---\n", - "\n", - "Name: test-manual-renamed\n", - "Id: 6b947f6e-dfcb-4df9-a2f5-9a9b6631753c\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: Allergies from parquet test\n", - "Id: 3c6d10b8-6e18-450a-a142-cbfd022703a2\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: test required int\n", - "Id: 46c79596-eb2e-47d5-8e1b-230643fa8431\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2\n", - "\n", - "---\n", - "\n", - "Name: file catalog view\n", - "Id: 5545fcf4-7bb5-4dc0-806b-61e2ac6681b3\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: Encounters\n", - "Id: 7ff8ee2a-7de5-4e6c-848a-80ba6a8bc189\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 248097\n", - "\n", - "---\n", - "\n", - "Name: drug_era\n", - "Id: 37c8bf91-c7e4-4283-9b75-868e315eb2b1\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 6652\n", - "\n", - "---\n", - "\n", - "Name: test rename\n", - "Id: bcacf8e8-8616-4042-803e-710e91462ad9\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: test-view-sql\n", - "Id: 55214476-57f5-424f-aa21-2ec518c75085\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test-view-existing\n", - "Id: a03a9fe8-8456-4bad-95a5-ef240f203dd1\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: nathan test\n", - "Id: 354620ea-82ed-4ae3-8bc9-89e31472c520\n", - "Description: test cirro file performance\n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: gtky_for_dashboard\n", - "Id: 1bb6a67c-45b2-48ac-b6b4-652ba87dbf07\n", - "Description: Denormalized data from the Getting To Know You survey\n", - "Sheet Type: TABLE\n", - "Total Row Count: 544\n", - "\n", - "---\n", - "\n", - "Name: test-kw-\n", - "Id: 9c500898-7dec-4309-b171-78434ee2c2e3\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: person\n", - "Id: 31b6f542-4ed0-4098-ad2e-6ef9b02142d7\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 1130\n", - "\n", - "---\n", - "\n", - "Name: visit_occurrence\n", - "Id: 4b8bacd5-ab92-4d28-b2a1-dd615c2066bf\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 32153\n", - "\n", - "---\n", - "\n", - "Name: test-required-field\n", - "Id: b39f20c0-cdbf-4d37-9eb5-f44f0d09fe23\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2\n", - "\n", - "---\n", - "\n", - "Name: Virtual Raw Check\n", - "Id: 9ac53e99-311d-4d2b-a7fc-1c0a850f2c10\n", - "Description: None\n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test\n", - "Id: ad80e579-6395-4c06-8890-a70b3f7b8b9e\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2\n", - "\n", - "---\n", - "\n", - "Name: test file\n", - "Id: 1ee4ab0a-445c-47d4-a900-aac0c5ae737a\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: Medications\n", - "Id: 828bc9e2-c04a-41a1-810d-5c4dcbca377d\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 76903\n", - "\n", - "---\n", - "\n", - "Name: concept\n", - "Id: cce071c6-c0d8-43b9-8f82-f13928f0770d\n", - "Description: Standard OMOP concept map with CPT4\n", - "Sheet Type: TABLE\n", - "Total Row Count: 6642023\n", - "\n", - "---\n", - "\n", - "Name: condition_era\n", - "Id: 982d963b-fe56-4ca0-9d57-79ea0d311675\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 7897\n", - "\n", - "---\n", - "\n", - "Name: DashboardCounts\n", - "Id: b2675672-53c6-4827-a82f-d5658e28661a\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 321\n", - "\n", - "---\n", - "\n", - "Name: test-file-project\n", - "Id: da60eaae-faaa-48b3-88d3-035c2ef90eb4\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: concept_relationship\n", - "Id: 9ac24ce7-a715-4a01-b54d-6826978e01e7\n", - "Description: OMOP concept relationship mapping\n", - "Sheet Type: TABLE\n", - "Total Row Count: 41672967\n", - "\n", - "---\n", - "\n", - "Name: Update display name mat view\n", - "Id: 54dc3697-0c96-40d7-b614-4152b3d90ff5\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: 980\n", - "\n", - "---\n", - "\n", - "Name: concept_ancestor\n", - "Id: fbc147a9-9122-4271-9195-4ffd584a6198\n", - "Description: OMOP concept ancestor mapping\n", - "Sheet Type: TABLE\n", - "Total Row Count: 79700074\n", - "\n", - "---\n", - "\n", - "Name: condition_occurrence\n", - "Id: fa53b18b-5278-428e-bb19-1d4b00113f8b\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 7900\n", - "\n", - "---\n", - "\n", - "Name: test_measurement_2\n", - "Id: 29b35c35-44d6-4e84-b6a5-2c4e7c64ffa3\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 338561\n", - "\n", - "---\n", - "\n", - "Name: test-view-aggregate\n", - "Id: 7e2f7eff-50d0-4bb3-8afb-0ca8aa4d1f97\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test json\n", - "Id: 7bb1f9a1-6ada-4ae8-b1c5-3bc3152a8c40\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2\n", - "\n", - "---\n", - "\n", - "Name: mdh_demo\n", - "Id: 9bfeff46-0aaa-4e56-b73e-2d8700b8240a\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 437\n", - "\n", - "---\n", - "\n", - "Name: Conditions\n", - "Id: d5859d3e-35a1-40ac-81f8-3e265093b7c0\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 42980\n", - "\n", - "---\n", - "\n", - "Name: test 2\n", - "Id: 5f7cb6fe-5f45-40d2-a681-f2e79b40106f\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: concept_class\n", - "Id: 34df6a38-8613-4f4f-ae03-31628a7dd274\n", - "Description: OMOP concept classes\n", - "Sheet Type: TABLE\n", - "Total Row Count: 433\n", - "\n", - "---\n", - "\n", - "Name: test wo kms\n", - "Id: a94b538c-ddac-40a9-91db-ec900fbc907a\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: 2024 patients\n", - "Id: 369c61eb-e2c6-4447-b378-1359eb1e12d8\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 1\n", - "\n", - "---\n", - "\n", - "Name: Allergies-test-move\n", - "Id: 2c5501a6-651c-4b9f-8ce6-f1b7c8af9329\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 976\n", - "\n", - "---\n", - "\n", - "Name: test-structured-view2\n", - "Id: ab4ad104-a1b8-44da-8839-9ac4f526a48f\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test_omop_ca\n", - "Id: 25230d41-3493-46d7-a71d-16bb9f750750\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: 78441023\n", - "\n", - "---\n", - "\n", - "Name: Allergies-test\n", - "Id: 44732f43-f51f-400e-a044-846edc28e045\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 2928\n", - "\n", - "---\n", - "\n", - "Name: test-if-see-arn\n", - "Id: a2019b2e-b7b8-44d0-995b-3962c67d7775\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 0\n", - "\n", - "---\n", - "\n", - "Name: test-view-modal-updates\n", - "Id: 9b5fbefe-4f4d-44d8-8ac1-08875965233e\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: vocabulary\n", - "Id: 986ee515-2ac6-40d8-a2e9-c9058a8cfc14\n", - "Description: OMOP vocabularies\n", - "Sheet Type: TABLE\n", - "Total Row Count: 62\n", - "\n", - "---\n", - "\n", - "Name: test-structured-view\n", - "Id: 57a3af7c-2421-4ec2-9007-10a99128082c\n", - "Description: \n", - "Sheet Type: VIEW\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: foo\n", - "Id: 5f0479d2-f432-4231-a494-352f7c31b9b9\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: None\n", - "\n", - "---\n", - "\n", - "Name: test 22\n", - "Id: 7a100096-ba53-4c79-9b7a-1b2e2c4f3083\n", - "Description: \n", - "Sheet Type: TABLE\n", - "Total Row Count: 3\n", - "\n", - "---\n", - "\n", - "Name: measurement\n", - "Id: 4e4239a3-af89-4153-9484-6644779ea04a\n", - "Description: 1k synthetic records\n", - "Sheet Type: TABLE\n", - "Total Row Count: 199514\n" - ] - } - ], - "source": [ - "print(project.list_sheets().description())" - ] - }, - { - "cell_type": "markdown", - "id": "28dd94a5", - "metadata": {}, - "source": "#### Defining the columns\n\nEach column has a name and a data type. A `required` column cannot be null, and a `unique` column rejects duplicate values." - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "4755a39c", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro_api_client.v1.models import ColumnDataType, ColumnDef\n", - "\n", - "columns = [\n", - " ColumnDef(name=\"sample_id\", data_type=ColumnDataType.STRING, required=True, unique=True),\n", - " ColumnDef(name=\"tissue\", data_type=ColumnDataType.STRING),\n", - " ColumnDef(name=\"condition\", data_type=ColumnDataType.STRING),\n", - " ColumnDef(name=\"read_count\", data_type=ColumnDataType.BIGINT)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "cedbeda1", - "metadata": {}, - "source": "#### Creating the sheet\n\n`sheet_type` says whether the sheet is a `TABLE` or a `VIEW`. `name` is what the sheet is called in Cirro, and `table_name` is how SQL queries refer to it. `namespace_name` groups related tables and cannot be changed after the sheet is created." - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e29db72", - "metadata": {}, - "outputs": [], - "source": "from cirro_api_client.v1.models import SheetCreationMode, TableSheetInput\n\nsheet = project.create_sheet(\n TableSheetInput(\n sheet_type=\"TABLE\",\n name=\"Example Samples\",\n description=\"Sample metadata used by the Sheets examples\",\n namespace_name=\"default\",\n table_name=\"example_samples\",\n sheet_creation_mode=SheetCreationMode.STANDARD,\n columns=columns\n )\n)\nprint(sheet)" - }, - { - "cell_type": "markdown", - "id": "8aeacb51", - "metadata": {}, - "source": [ - "#### Deleting a sheet\n", - "\n", - "Once you are done with the examples, delete the sheet along with the rows it holds." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "510771b4", - "metadata": {}, - "outputs": [], - "source": [ - "# sheet.delete()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "cirro-sdk", - "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.13.17" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/samples/Inserting_sheet_rows.ipynb b/samples/Inserting_sheet_rows.ipynb deleted file mode 100644 index 67d13f9..0000000 --- a/samples/Inserting_sheet_rows.ipynb +++ /dev/null @@ -1,117 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7f0d4b41", - "metadata": {}, - "source": "## Inserting rows into a sheet\n\nEach row is a mapping of column name to value, and columns left out of a row are null." - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5fb8d91d", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro import DataPortal\n", - "\n", - "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test Project\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1c86c9d", - "metadata": {}, - "outputs": [], - "source": [ - "sheet = project.get_sheet_by_name(\"Example Samples\")" - ] - }, - { - "cell_type": "markdown", - "id": "8f7f1bd7", - "metadata": {}, - "source": [ - "#### Inserting a few rows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9099602a", - "metadata": {}, - "outputs": [], - "source": [ - "inserted = sheet.insert_rows([\n", - " {\"sample_id\": \"S1\", \"tissue\": \"liver\", \"condition\": \"control\", \"read_count\": 21000000},\n", - " {\"sample_id\": \"S2\", \"tissue\": \"liver\", \"condition\": \"treated\", \"read_count\": 19500000},\n", - " {\"sample_id\": \"S3\", \"tissue\": \"lung\", \"condition\": \"control\"}\n", - "])\n", - "print(f\"Inserted {inserted} rows\")" - ] - }, - { - "cell_type": "markdown", - "id": "bc4ce616", - "metadata": {}, - "source": [ - "#### Inserting rows from a DataFrame\n", - "\n", - "`to_dict(orient=\"records\")` turns each row of a DataFrame into the mapping `insert_rows` expects." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2fcc24bd", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "\n", - "df = pd.DataFrame({\n", - " \"sample_id\": [\"S4\", \"S5\"],\n", - " \"tissue\": [\"kidney\", \"lung\"],\n", - " \"condition\": [\"treated\", \"treated\"],\n", - " \"read_count\": [18000000, 22500000]\n", - "})\n", - "\n", - "inserted = sheet.insert_rows(df.to_dict(orient=\"records\"))\n", - "print(f\"Inserted {inserted} rows\")" - ] - }, - { - "cell_type": "markdown", - "id": "ebe88d48", - "metadata": {}, - "source": [ - "#### Checking the result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f8f11a0", - "metadata": {}, - "outputs": [], - "source": [ - "sheet.to_dataframe()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/samples/Querying_sheets.ipynb b/samples/Querying_sheets.ipynb deleted file mode 100644 index 6a4ea15..0000000 --- a/samples/Querying_sheets.ipynb +++ /dev/null @@ -1,331 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "16144707", - "metadata": {}, - "source": [ - "## Querying sheets with SQL\n", - "\n", - "`query_sheets` runs SQL across all the sheets in a project and returns the result as a DataFrame. Refer to a sheet by its `table_name` (here `example_samples`), not its display name." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "d7795336", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro import DataPortal\n", - "\n", - "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test project 1\")" - ] - }, - { - "cell_type": "markdown", - "id": "5181cd98", - "metadata": {}, - "source": [ - "#### Filtering rows" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "621129df", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sample_idconditionread_count
0S1control21000000
1S2treated19500000
\n", - "" - ], - "text/plain": [ - " sample_id condition read_count\n", - "0 S1 control 21000000\n", - "1 S2 treated 19500000" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "project.query_sheets(\"\"\"\n", - " SELECT sample_id, condition, read_count\n", - " FROM example_samples\n", - " WHERE tissue = 'liver'\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "id": "301f075e", - "metadata": {}, - "source": [ - "#### Summarizing rows" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "17110b90", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
conditionsamplesmean_reads
0treated320000000.0
1control221000000.0
\n", - "
" - ], - "text/plain": [ - " condition samples mean_reads\n", - "0 treated 3 20000000.0\n", - "1 control 2 21000000.0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "project.query_sheets(\"\"\"\n", - " SELECT condition, COUNT(*) AS samples, AVG(read_count) AS mean_reads\n", - " FROM example_samples\n", - " GROUP BY condition\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "id": "ba7112f3", - "metadata": {}, - "source": [ - "#### Limiting a large result\n", - "\n", - "`query_sheets` reads the whole result unless `max_rows` stops it early." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "55630001", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
sample_idtissueconditionread_countsys_created_atsys_updated_at
0S1livercontrol210000002026-10-23T20:14:51.671965Z2026-10-23T20:14:51.671965Z
1S2livertreated195000002026-10-23T20:14:51.671965Z2026-10-23T20:14:51.671965Z
\n", - "
" - ], - "text/plain": [ - " sample_id tissue condition read_count sys_created_at \\\n", - "0 S1 liver control 21000000 2026-10-23T20:14:51.671965Z \n", - "1 S2 liver treated 19500000 2026-10-23T20:14:51.671965Z \n", - "\n", - " sys_updated_at \n", - "0 2026-10-23T20:14:51.671965Z \n", - "1 2026-10-23T20:14:51.671965Z " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "project.query_sheets(\"SELECT * FROM example_samples\", max_rows=2)" - ] - }, - { - "cell_type": "markdown", - "id": "e9fb71d1", - "metadata": {}, - "source": [ - "#### Querying from a Cirro workspace\n", - "\n", - "Inside a workspace with Sheets enabled, `cirro.helpers.workspace` queries the project's sheets directly with DuckDB, with no Cirro login. Refer to a sheet as `sheets..`. This needs the `sheets` extra (`pip install \"cirro[sheets]\"`), and the cell below only runs inside a workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07212bc7", - "metadata": {}, - "outputs": [ - { - "ename": "RuntimeError", - "evalue": "connect_sheets() only works inside a Cirro workspace with Sheets enabled. Elsewhere, use project.query_sheets().", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mRuntimeError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m cirro.helpers.workspace \u001b[38;5;28;01mimport\u001b[39;00m query_sheets\n\u001b[32m 2\u001b[39m \n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m query_sheets(\u001b[33m\"SELECT tissue, COUNT(*) AS samples FROM sheets.default.example_samples GROUP BY tissue\"\u001b[39m)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/code/Cirro-sdk-python/cirro/helpers/workspace.py:71\u001b[39m, in \u001b[36mquery_sheets\u001b[39m\u001b[34m(sql)\u001b[39m\n\u001b[32m 57\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mquery_sheets\u001b[39m(sql: \u001b[38;5;28mstr\u001b[39m) -> \u001b[33m'\u001b[39m\u001b[33mDataFrame\u001b[39m\u001b[33m'\u001b[39m:\n\u001b[32m 58\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 59\u001b[39m \u001b[33;03m Run a query against the project's Sheets and return the result as a Pandas DataFrame.\u001b[39;00m\n\u001b[32m 60\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 69\u001b[39m \u001b[33;03m `pandas.DataFrame`\u001b[39;00m\n\u001b[32m 70\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m71\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[30;43mconnect_sheets\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m \u001b[38;5;28;01mas\u001b[39;00m conn:\n\u001b[32m 72\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m conn.sql(sql).df()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/code/Cirro-sdk-python/cirro/helpers/workspace.py:42\u001b[39m, in \u001b[36mconnect_sheets\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 40\u001b[39m \u001b[38;5;66;03m# Cirro sets this, with the project, account and region, on workspaces whose role can read Sheets\u001b[39;00m\n\u001b[32m 41\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m os.environ.get(\u001b[33m\"\u001b[39m\u001b[33mCIRRO_SHEETS_ENABLED\u001b[39m\u001b[33m\"\u001b[39m) != \u001b[33m\"\u001b[39m\u001b[33mtrue\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m---> \u001b[39m\u001b[32m42\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mconnect_sheets() only works inside a Cirro workspace with Sheets enabled. \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 43\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mElsewhere, use project.query_sheets().\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 44\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 45\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mduckdb\u001b[39;00m\n", - "\u001b[31mRuntimeError\u001b[39m: connect_sheets() only works inside a Cirro workspace with Sheets enabled. Elsewhere, use project.query_sheets()." - ] - } - ], - "source": [ - "# from cirro.helpers.workspace import query_sheets\n", - "\n", - "# query_sheets(\"SELECT tissue, COUNT(*) AS samples FROM sheets.default.example_samples GROUP BY tissue\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ea10db8f", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "cirro-sdk", - "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.13.17" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/samples/Reading_a_sheet.ipynb b/samples/Reading_a_sheet.ipynb deleted file mode 100644 index a0a81c1..0000000 --- a/samples/Reading_a_sheet.ipynb +++ /dev/null @@ -1,119 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "32bcb218", - "metadata": {}, - "source": [ - "## Reading a sheet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8a363b7", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro import DataPortal\n", - "\n", - "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test Project\")" - ] - }, - { - "cell_type": "markdown", - "id": "1aedddd1", - "metadata": {}, - "source": [ - "#### Finding a sheet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1402af8", - "metadata": {}, - "outputs": [], - "source": [ - "sheet = project.get_sheet_by_name(\"Example Samples\")\n", - "print(sheet)" - ] - }, - { - "cell_type": "markdown", - "id": "23dbdb6f", - "metadata": {}, - "source": [ - "#### Inspecting the columns" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6dae1513", - "metadata": {}, - "outputs": [], - "source": [ - "for column in sheet.columns:\n", - " print(column.name, column.data_type)" - ] - }, - { - "cell_type": "markdown", - "id": "2458f42b", - "metadata": {}, - "source": [ - "#### Reading rows into a DataFrame\n", - "\n", - "`to_dataframe` pages through the whole sheet. The first column, `_row_id`, identifies each row for `update_rows` and `delete_rows`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e4d2c5bb", - "metadata": {}, - "outputs": [], - "source": [ - "df = sheet.to_dataframe()\n", - "df.head()" - ] - }, - { - "cell_type": "markdown", - "id": "4554e32c", - "metadata": {}, - "source": [ - "#### Reading part of a sheet\n", - "\n", - "Sort the rows and stop after a set number of them. To filter rows, see `Querying_sheets.ipynb`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35e35b2c", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro_api_client.v1.models import SqlSortOrder\n", - "\n", - "sheet.to_dataframe(max_rows=3, order_by=\"read_count\", order=SqlSortOrder.DESC)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/samples/Updating_sheet_rows.ipynb b/samples/Updating_sheet_rows.ipynb deleted file mode 100644 index f45ff36..0000000 --- a/samples/Updating_sheet_rows.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "4f106507", - "metadata": {}, - "source": [ - "## Updating rows in a sheet\n", - "\n", - "Rows are identified by `_row_id`, which `to_dataframe` returns as the first column. Read the sheet, find the rows to change, then pass their `_row_id`s to `update_rows` or `delete_rows`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "effbe5f9", - "metadata": {}, - "outputs": [], - "source": [ - "from cirro import DataPortal\n", - "\n", - "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test Project\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a08cc9d6", - "metadata": {}, - "outputs": [], - "source": [ - "sheet = project.get_sheet_by_name(\"Example Samples\")" - ] - }, - { - "cell_type": "markdown", - "id": "9a864c9f", - "metadata": {}, - "source": [ - "#### Finding the rows to change" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f02ab92", - "metadata": {}, - "outputs": [], - "source": [ - "df = sheet.to_dataframe()\n", - "df" - ] - }, - { - "cell_type": "markdown", - "id": "c3dad806", - "metadata": {}, - "source": [ - "#### Updating one row\n", - "\n", - "Only the columns you name are written; the rest of the row is left alone." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dc528020", - "metadata": {}, - "outputs": [], - "source": [ - "row_id = int(df.loc[df[\"sample_id\"] == \"S3\", \"_row_id\"].iloc[0])\n", - "\n", - "sheet.update_rows({row_id: {\"read_count\": 20100000}})" - ] - }, - { - "cell_type": "markdown", - "id": "853566de", - "metadata": {}, - "source": [ - "#### Updating several rows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d2c51ced", - "metadata": {}, - "outputs": [], - "source": [ - "# Mark every lung sample as treated\n", - "lung = df.loc[df[\"tissue\"] == \"lung\", \"_row_id\"]\n", - "\n", - "updated = sheet.update_rows({int(row_id): {\"condition\": \"treated\"} for row_id in lung})\n", - "print(f\"Updated {updated} rows\")" - ] - }, - { - "cell_type": "markdown", - "id": "3a6b5e40", - "metadata": {}, - "source": [ - "#### Deleting rows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "343236ad", - "metadata": {}, - "outputs": [], - "source": [ - "to_delete = df.loc[df[\"sample_id\"] == \"S5\", \"_row_id\"]\n", - "\n", - "deleted = sheet.delete_rows([int(row_id) for row_id in to_delete])\n", - "print(f\"Deleted {deleted} rows\")" - ] - }, - { - "cell_type": "markdown", - "id": "ff161d41", - "metadata": {}, - "source": [ - "#### Checking the result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5f8c93a5", - "metadata": {}, - "outputs": [], - "source": [ - "sheet.to_dataframe()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/samples/Working_with_sheets.ipynb b/samples/Working_with_sheets.ipynb new file mode 100644 index 0000000..fd759af --- /dev/null +++ b/samples/Working_with_sheets.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0f7a31b4", + "metadata": {}, + "source": [ + "## Working with sheets\n", + "\n", + "Sheets are tables of structured data held alongside a project. This notebook follows one sheet from start to finish: it creates a table of sample metadata, adds the samples, reads and summarizes them, corrects a few rows, and then deletes the sheet." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82aaadbb", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro import DataPortal\n", + "\n", + "portal = DataPortal()\n", + "project = portal.get_project_by_name(\"Test Project\")" + ] + }, + { + "cell_type": "markdown", + "id": "4371a4bd", + "metadata": {}, + "source": [ + "#### Creating a sheet\n", + "\n", + "Each column has a name and a data type. `sample_id` is `required`, so it cannot be null, and `unique`, so no two rows can share it.\n", + "\n", + "`sheet_type` says whether the sheet is a `TABLE`, which holds rows you write to it, or a `VIEW`, which is materialized from a query. `table_name` is how SQL queries refer to the sheet, and `namespace_name` groups related tables and cannot be changed later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df774c3e", + "metadata": {}, + "outputs": [], + "source": [ + "from cirro_api_client.v1.models import ColumnDataType, ColumnDef, SheetCreationMode, TableSheetInput\n", + "\n", + "sheet = project.create_sheet(\n", + " TableSheetInput(\n", + " sheet_type=\"TABLE\",\n", + " name=\"Example Samples\",\n", + " description=\"Sample metadata used by the Sheets example\",\n", + " namespace_name=\"default\",\n", + " table_name=\"example_samples\",\n", + " sheet_creation_mode=SheetCreationMode.STANDARD,\n", + " columns=[\n", + " ColumnDef(name=\"sample_id\", data_type=ColumnDataType.STRING, required=True, unique=True),\n", + " ColumnDef(name=\"tissue\", data_type=ColumnDataType.STRING),\n", + " ColumnDef(name=\"condition\", data_type=ColumnDataType.STRING),\n", + " ColumnDef(name=\"read_count\", data_type=ColumnDataType.BIGINT)\n", + " ]\n", + " )\n", + ")\n", + "print(sheet)" + ] + }, + { + "cell_type": "markdown", + "id": "511771f8", + "metadata": {}, + "source": [ + "#### Inserting rows\n", + "\n", + "Each row maps column names to values, and columns left out are null. S3's read count isn't in yet, so it is left out. To insert from a DataFrame, pass `df.to_dict(orient=\"records\")`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed7f5023", + "metadata": {}, + "outputs": [], + "source": [ + "inserted = sheet.insert_rows([\n", + " {\"sample_id\": \"S1\", \"tissue\": \"liver\", \"condition\": \"control\", \"read_count\": 21000000},\n", + " {\"sample_id\": \"S2\", \"tissue\": \"liver\", \"condition\": \"treated\", \"read_count\": 19500000},\n", + " {\"sample_id\": \"S3\", \"tissue\": \"lung\", \"condition\": \"control\"},\n", + " {\"sample_id\": \"S4\", \"tissue\": \"kidney\", \"condition\": \"treated\", \"read_count\": 18000000},\n", + " {\"sample_id\": \"S5\", \"tissue\": \"lung\", \"condition\": \"treated\", \"read_count\": 22500000}\n", + "])\n", + "print(f\"Inserted {inserted} rows\")" + ] + }, + { + "cell_type": "markdown", + "id": "e69dd763", + "metadata": {}, + "source": [ + "#### Reading the sheet\n", + "\n", + "`to_dataframe` reads the whole sheet. Its first column, `_row_id`, identifies each row, and is how the updates and deletes below pick out the rows to change." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df662b09", + "metadata": {}, + "outputs": [], + "source": [ + "df = sheet.to_dataframe()\n", + "row_ids = {sample_id: int(row_id) for sample_id, row_id in zip(df[\"sample_id\"], df[\"_row_id\"])}\n", + "df" + ] + }, + { + "cell_type": "markdown", + "id": "704200ab", + "metadata": {}, + "source": [ + "#### Querying with SQL\n", + "\n", + "`query_sheets` runs SQL across the project's sheets and returns a DataFrame. Refer to a sheet by its `table_name`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e6e1cd9", + "metadata": {}, + "outputs": [], + "source": [ + "project.query_sheets(\"\"\"\n", + " SELECT condition, COUNT(*) AS samples, AVG(read_count) AS mean_reads\n", + " FROM example_samples\n", + " GROUP BY condition\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "18ef34e7", + "metadata": {}, + "source": [ + "#### Updating a row\n", + "\n", + "S3's read count is in now. Only the columns you name are written; the rest of the row is left alone." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "339c820f", + "metadata": {}, + "outputs": [], + "source": [ + "sheet.update_rows({row_ids[\"S3\"]: {\"read_count\": 20100000}})" + ] + }, + { + "cell_type": "markdown", + "id": "217672d9", + "metadata": {}, + "source": [ + "#### Deleting a row\n", + "\n", + "S5 failed QC, so remove it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bacd687", + "metadata": {}, + "outputs": [], + "source": [ + "sheet.delete_rows([row_ids[\"S5\"]])\n", + "\n", + "sheet.to_dataframe()" + ] + }, + { + "cell_type": "markdown", + "id": "a0ea9149", + "metadata": {}, + "source": [ + "#### Deleting the sheet\n", + "\n", + "Delete the sheet, along with its rows, once you are done with it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3db31738", + "metadata": {}, + "outputs": [], + "source": [ + "sheet.delete()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 2c9ffb70920cf4b36837f9bff5f34437ab5c3e01 Mon Sep 17 00:00:00 2001 From: Kevin Winkler Date: Fri, 23 Oct 2026 14:11:14 -0700 Subject: [PATCH 7/7] Execute cells. --- samples/Working_with_sheets.ipynb | 335 ++++++++++++++++++++++++++++-- 1 file changed, 318 insertions(+), 17 deletions(-) diff --git a/samples/Working_with_sheets.ipynb b/samples/Working_with_sheets.ipynb index fd759af..e3dabc8 100644 --- a/samples/Working_with_sheets.ipynb +++ b/samples/Working_with_sheets.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "82aaadbb", "metadata": {}, "outputs": [], @@ -20,7 +20,7 @@ "from cirro import DataPortal\n", "\n", "portal = DataPortal()\n", - "project = portal.get_project_by_name(\"Test Project\")" + "project = portal.get_project_by_name(\"Test project 1\")" ] }, { @@ -37,10 +37,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "df774c3e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Example Samples\n", + "Id: 788a504e-0484-49d6-9d9a-98a11e0c584a\n", + "Description: Sample metadata used by the Sheets example\n", + "Sheet Type: TABLE\n", + "Total Row Count: 0\n" + ] + } + ], "source": [ "from cirro_api_client.v1.models import ColumnDataType, ColumnDef, SheetCreationMode, TableSheetInput\n", "\n", @@ -75,10 +87,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "ed7f5023", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Inserted 5 rows\n" + ] + } + ], "source": [ "inserted = sheet.insert_rows([\n", " {\"sample_id\": \"S1\", \"tissue\": \"liver\", \"condition\": \"control\", \"read_count\": 21000000},\n", @@ -102,10 +122,116 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "df662b09", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
_row_idsample_idtissueconditionread_countsys_created_atsys_updated_at
00S1livercontrol21000000.02026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
11S2livertreated19500000.02026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
22S3lungcontrolNaN2026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
33S4kidneytreated18000000.02026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
44S5lungtreated22500000.02026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
\n", + "" + ], + "text/plain": [ + " _row_id sample_id tissue condition read_count \\\n", + "0 0 S1 liver control 21000000.0 \n", + "1 1 S2 liver treated 19500000.0 \n", + "2 2 S3 lung control NaN \n", + "3 3 S4 kidney treated 18000000.0 \n", + "4 4 S5 lung treated 22500000.0 \n", + "\n", + " sys_created_at sys_updated_at \n", + "0 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "1 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "2 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "3 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "4 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "df = sheet.to_dataframe()\n", "row_ids = {sample_id: int(row_id) for sample_id, row_id in zip(df[\"sample_id\"], df[\"_row_id\"])}\n", @@ -124,10 +250,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "9e6e1cd9", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
conditionsamplesmean_reads
0treated320000000.0
1control221000000.0
\n", + "
" + ], + "text/plain": [ + " condition samples mean_reads\n", + "0 treated 3 20000000.0\n", + "1 control 2 21000000.0" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "project.query_sheets(\"\"\"\n", " SELECT condition, COUNT(*) AS samples, AVG(read_count) AS mean_reads\n", @@ -148,10 +328,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "339c820f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sheet.update_rows({row_ids[\"S3\"]: {\"read_count\": 20100000}})" ] @@ -168,10 +359,104 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "0bacd687", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
_row_idsample_idtissueconditionread_countsys_created_atsys_updated_at
00S1livercontrol210000002026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
11S2livertreated195000002026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
23S4kidneytreated180000002026-10-23T20:47:38.903285Z2026-10-23T20:47:38.903285Z
32S3lungcontrol201000002026-10-23T20:47:38.903285Z2026-10-23T20:48:11.223366Z
\n", + "
" + ], + "text/plain": [ + " _row_id sample_id tissue condition read_count \\\n", + "0 0 S1 liver control 21000000 \n", + "1 1 S2 liver treated 19500000 \n", + "2 3 S4 kidney treated 18000000 \n", + "3 2 S3 lung control 20100000 \n", + "\n", + " sys_created_at sys_updated_at \n", + "0 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "1 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "2 2026-10-23T20:47:38.903285Z 2026-10-23T20:47:38.903285Z \n", + "3 2026-10-23T20:47:38.903285Z 2026-10-23T20:48:11.223366Z " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "sheet.delete_rows([row_ids[\"S5\"]])\n", "\n", @@ -190,24 +475,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "3db31738", "metadata": {}, "outputs": [], "source": [ "sheet.delete()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09366127", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "cirro-sdk", "language": "python", "name": "python3" }, "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", "name": "python", - "pygments_lexer": "ipython3" + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.17" } }, "nbformat": 4,