diff --git a/python/cocoindex/cli.py b/python/cocoindex/cli.py index 3ef57738d..5c95be7f8 100644 --- a/python/cocoindex/cli.py +++ b/python/cocoindex/cli.py @@ -1,7 +1,9 @@ +import asyncio import click import datetime from rich.console import Console +from rich.table import Table from . import flow, lib, setting from .setup import sync_setup, drop_setup, flow_names_with_setup, apply_setup_changes @@ -56,11 +58,26 @@ def ls(show_all: bool): @click.option("--color/--no-color", default=True) def show(flow_name: str | None, color: bool): """ - Show the flow spec in a readable format with colored output. + Show the flow spec in a readable format with colored output, + including the schema. """ - fl = _flow_by_name(flow_name) + flow = _flow_by_name(flow_name) console = Console(no_color=not color) - console.print(fl._render_text()) + console.print(flow._render_text()) + + table = Table( + title=f"Schema for Flow: {flow.name}", + show_header=True, + header_style="bold magenta" + ) + table.add_column("Field", style="cyan") + table.add_column("Type", style="green") + table.add_column("Attributes", style="yellow") + + for field_name, field_type, attr_str in flow._render_schema(): + table.add_row(field_name, field_type, attr_str) + + console.print(table) @cli.command() def setup(): diff --git a/python/cocoindex/flow.py b/python/cocoindex/flow.py index c24016455..cbc095eac 100644 --- a/python/cocoindex/flow.py +++ b/python/cocoindex/flow.py @@ -503,6 +503,9 @@ def _render_text(self) -> Text: return self._format_flow(flow_dict) except json.JSONDecodeError: return Text(flow_spec_str) + + def _render_schema(self) -> list[tuple[str, str, str]]: + return self._lazy_engine_flow().get_schema() def __str__(self): return str(self._render_text()) diff --git a/src/py/mod.rs b/src/py/mod.rs index 3e21262ff..e4283681f 100644 --- a/src/py/mod.rs +++ b/src/py/mod.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::base::schema::{FieldSchema, ValueType}; use crate::base::spec::VectorSimilarityMetric; use crate::execution::query; use crate::lib_context::{clear_lib_context, get_auth_registry, init_lib_context}; @@ -13,6 +14,7 @@ use pyo3::{exceptions::PyException, prelude::*}; use pyo3_async_runtimes::tokio::future_into_py; use std::collections::btree_map; use std::fmt::Write; +use std::sync::Arc; mod convert; pub use convert::*; @@ -193,6 +195,58 @@ impl Flow { Ok(()) }) } + + pub fn get_schema(&self) -> Vec<(String, String, String)> { + let schema = &self.0.flow.data_schema; + let mut result = Vec::new(); + + fn process_fields( + fields: &[FieldSchema], + prefix: &str, + result: &mut Vec<(String, String, String)>, + ) { + for field in fields { + let field_name = format!("{}{}", prefix, field.name); + + let mut field_type = match &field.value_type.typ { + ValueType::Basic(basic) => format!("{}", basic), + ValueType::Table(t) => format!("{}", t.kind), + ValueType::Struct(_) => "Struct".to_string(), + }; + + if field.value_type.nullable { + field_type.push('?'); + } + + let attr_str = if field.value_type.attrs.is_empty() { + String::new() + } else { + field + .value_type + .attrs + .keys() + .map(|k| k.to_string()) + .collect::>() + .join(", ") + }; + + result.push((field_name.clone(), field_type, attr_str)); + + match &field.value_type.typ { + ValueType::Struct(s) => { + process_fields(&s.fields, &format!("{}.", field_name), result); + } + ValueType::Table(t) => { + process_fields(&t.row.fields, &format!("{}[].", field_name), result); + } + ValueType::Basic(_) => {} + } + } + } + + process_fields(&schema.schema.fields, "", &mut result); + result + } } #[pyclass]