Skip to content

Commit 1065c37

Browse files
committed
feat(cli): enhance flow display with schema rendering support
1 parent fd4bfcc commit 1065c37

File tree

3 files changed

+77
-59
lines changed

3 files changed

+77
-59
lines changed

python/cocoindex/cli.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import asyncio
12
import click
23
import datetime
34

45
from rich.console import Console
6+
from rich.table import Table
57

68
from . import flow, lib, setting
79
from .setup import sync_setup, drop_setup, flow_names_with_setup, apply_setup_changes
@@ -54,27 +56,26 @@ def ls(show_all: bool):
5456
@cli.command()
5557
@click.argument("flow_name", type=str, required=False)
5658
@click.option("--color/--no-color", default=True)
57-
@click.option(
58-
"--schema", is_flag=True, show_default=True, default=False,
59-
help="Also show the schema of the flow.")
60-
def show(flow_name: str | None, color: bool, schema: bool):
59+
def show(flow_name: str | None, color: bool):
6160
"""
62-
Show the flow spec in a readable format with colored output.
63-
Optionally display the schema if --schema is provided.
61+
Show the flow spec in a readable format with colored output,
62+
including the schema.
6463
"""
65-
from rich.table import Table
6664
flow = _flow_by_name(flow_name)
6765
console = Console(no_color=not color)
6866
console.print(flow._render_text())
69-
if schema:
67+
68+
async def render_schema_and_print():
7069
table = Table(title=f"Schema for Flow: {flow.name}", show_header=True, header_style="bold magenta")
7170
table.add_column("Field", style="cyan")
7271
table.add_column("Type", style="green")
7372
table.add_column("Attributes", style="yellow")
74-
for field_name, field_type, attr_str in flow._render_schema():
73+
for field_name, field_type, attr_str in await flow._render_schema():
7574
table.add_row(field_name, field_type, attr_str)
7675
console.print(table)
7776

77+
asyncio.run(render_schema_and_print())
78+
7879
@cli.command()
7980
def setup():
8081
"""

python/cocoindex/flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,11 +504,11 @@ def _render_text(self) -> Text:
504504
except json.JSONDecodeError:
505505
return Text(flow_spec_str)
506506

507-
def _render_schema(self) -> list[tuple[str, str, str]]:
507+
async def _render_schema(self) -> list[tuple[str, str, str]]:
508508
"""
509509
Render the schema as a list of (field_name, field_type, attributes) tuples.
510510
"""
511-
return _engine.get_flow_schema(self.name)
511+
return await _engine.format_flow_schema(self.name)
512512

513513
def __str__(self):
514514
return str(self._render_text())

src/py/mod.rs

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use crate::prelude::*;
22

3-
use crate::base::schema::{DataSchema, EnrichedValueType, FieldSchema, ValueType};
3+
use crate::base::schema::{BasicValueType, FieldSchema, ValueType};
44
use crate::base::spec::VectorSimilarityMetric;
55
use crate::execution::query;
66
use crate::lib_context::{clear_lib_context, get_auth_registry, init_lib_context};
77
use crate::ops::interface::{QueryResult, QueryResults};
88
use crate::ops::py_factory::PyOpArgSchema;
99
use crate::ops::{interface::ExecutorFactory, py_factory::PyFunctionFactory, register_factory};
1010
use crate::server::{self, ServerSettings};
11+
use crate::service::flows::get_flow_schema;
1112
use crate::settings::Settings;
1213
use crate::setup;
14+
use axum::extract::{Path, State};
1315
use pyo3::{exceptions::PyException, prelude::*};
1416
use pyo3_async_runtimes::tokio::future_into_py;
1517
use std::collections::btree_map;
@@ -368,58 +370,73 @@ fn add_auth_entry(key: String, value: Pythonized<serde_json::Value>) -> PyResult
368370
}
369371

370372
#[pyfunction]
371-
fn get_flow_schema(_py: Python<'_>, flow_name: String) -> PyResult<Vec<(String, String, String)>> {
372-
let lib_context = get_lib_context().map_err(|e| PyException::new_err(e.to_string()))?;
373-
let flow_ctx = lib_context
374-
.get_flow_context(&flow_name)
375-
.map_err(|e| PyException::new_err(e.to_string()))?;
376-
let schema = flow_ctx.flow.data_schema.clone();
377-
378-
let mut result = Vec::new();
379-
fn process_fields(
380-
fields: &[FieldSchema],
381-
prefix: &str,
382-
result: &mut Vec<(String, String, String)>,
383-
) {
384-
for field in fields {
385-
let field_name = format!("{}{}", prefix, field.name);
386-
387-
let mut field_type = format!("{}", field.value_type.typ);
388-
if field.value_type.nullable {
389-
field_type.push('?');
390-
}
373+
fn format_flow_schema<'py>(py: Python<'py>, flow_name: String) -> PyResult<Bound<'py, PyAny>> {
374+
future_into_py(py, async move {
375+
let lib_context = get_lib_context().into_py_result()?;
376+
let schema = get_flow_schema(Path(flow_name), State(lib_context))
377+
.await
378+
.into_py_result()?;
391379

392-
let attr_str = if field.value_type.attrs.is_empty() {
393-
String::new()
394-
} else {
395-
field
396-
.value_type
397-
.attrs
398-
.iter()
399-
.map(|(k, v)| {
400-
let v_str = serde_json::to_string(v).unwrap_or_default();
401-
format!("{}: {}", k, v_str.chars().take(50).collect::<String>())
402-
})
403-
.collect::<Vec<_>>()
404-
.join(", ")
405-
};
406-
407-
result.push((field_name.clone(), field_type, attr_str));
408-
409-
match &field.value_type.typ {
410-
ValueType::Struct(s) => {
411-
process_fields(&s.fields, &format!("{}.", field_name), result);
380+
let mut result = Vec::new();
381+
382+
fn process_fields(
383+
fields: &[FieldSchema],
384+
prefix: &str,
385+
result: &mut Vec<(String, String, String)>,
386+
) {
387+
for field in fields {
388+
let field_name = format!("{}{}", prefix, field.name);
389+
390+
let mut field_type = match &field.value_type.typ {
391+
ValueType::Basic(basic) => match basic {
392+
BasicValueType::Vector(v) => {
393+
let dim = v.dimension.map_or("*".to_string(), |d| d.to_string());
394+
let elem = match *v.element_type {
395+
BasicValueType::Float32 => "Float32",
396+
BasicValueType::Float64 => "Float64",
397+
_ => "Unknown",
398+
};
399+
format!("Vector[{}, {}]", dim, elem)
400+
}
401+
other => format!("{:?}", other),
402+
},
403+
ValueType::Table(t) => format!("{:?}", t.kind),
404+
ValueType::Struct(_) => "Struct".to_string(),
405+
};
406+
407+
if field.value_type.nullable {
408+
field_type.push('?');
412409
}
413-
ValueType::Table(t) => {
414-
process_fields(&t.row.fields, &format!("{}.", field_name), result);
410+
411+
let attr_str = if field.value_type.attrs.is_empty() {
412+
String::new()
413+
} else {
414+
field
415+
.value_type
416+
.attrs
417+
.keys()
418+
.map(|k| k.to_string())
419+
.collect::<Vec<_>>()
420+
.join(", ")
421+
};
422+
423+
result.push((field_name.clone(), field_type, attr_str));
424+
425+
match &field.value_type.typ {
426+
ValueType::Struct(s) => {
427+
process_fields(&s.fields, &format!("{}.", field_name), result);
428+
}
429+
ValueType::Table(t) => {
430+
process_fields(&t.row.fields, &format!("{}.", field_name), result);
431+
}
432+
ValueType::Basic(_) => {}
415433
}
416-
ValueType::Basic(_) => {}
417434
}
418435
}
419-
}
420436

421-
process_fields(&schema.schema.fields, "", &mut result);
422-
Ok(result)
437+
process_fields(&schema.schema.fields, "", &mut result);
438+
Ok(result)
439+
})
423440
}
424441

425442
/// A Python module implemented in Rust.
@@ -435,7 +452,7 @@ fn cocoindex_engine(m: &Bound<'_, PyModule>) -> PyResult<()> {
435452
m.add_function(wrap_pyfunction!(apply_setup_changes, m)?)?;
436453
m.add_function(wrap_pyfunction!(flow_names_with_setup, m)?)?;
437454
m.add_function(wrap_pyfunction!(add_auth_entry, m)?)?;
438-
m.add_function(wrap_pyfunction!(get_flow_schema, m)?)?;
455+
m.add_function(wrap_pyfunction!(format_flow_schema, m)?)?;
439456

440457
m.add_class::<builder::flow_builder::FlowBuilder>()?;
441458
m.add_class::<builder::flow_builder::DataCollector>()?;

0 commit comments

Comments
 (0)