Skip to content

Commit 7b8aa4a

Browse files
committed
Make defensive, expose common protected functions
1 parent b023510 commit 7b8aa4a

File tree

4 files changed

+164
-33
lines changed

4 files changed

+164
-33
lines changed

src/client/content/config/tabs/databases.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def display_databases() -> None:
258258
selected_database_alias = st.selectbox(
259259
"Current Database:",
260260
options=list(database_lookup.keys()),
261-
index=list(database_lookup.keys()).index(state.client_settings["database"]["alias"]),
261+
index=list(database_lookup.keys()).index(state.client_settings.get("database", {}).get("alias")),
262262
key="selected_database",
263263
on_change=st_common.update_client_settings("database"),
264264
)

src/client/content/config/tabs/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config):
302302
sys_prompt=f"{sys_prompt}",
303303
ll_model=ll_config,
304304
vector_search=embed_config,
305-
database_config=database_lookup[state.client_settings["database"]["alias"]],
305+
database_config=database_lookup[state.client_settings.get("database", {}).get("alias")],
306306
)
307307

308308
if file_name.endswith(".yaml"):
@@ -313,7 +313,7 @@ def spring_ai_obaas(src_dir, file_name, provider, ll_config, embed_config):
313313
sys_prompt=sys_prompt,
314314
ll_model=ll_config,
315315
vector_search=embed_config,
316-
database_config=database_lookup[state.client_settings["database"]["alias"]],
316+
database_config=database_lookup[state.client_settings.get("database", {}).get("alias")],
317317
)
318318

319319
yaml_data = yaml.safe_load(formatted_content)

src/client/utils/st_common.py

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def is_db_configured() -> bool:
117117
(
118118
config.get("connected")
119119
for config in state.database_configs
120-
if config.get("name") == state.client_settings["database"]["alias"]
120+
if config.get("name") == state.client_settings.get("database", {}).get("alias")
121121
),
122122
False,
123123
)
@@ -274,7 +274,7 @@ def _update_set_tool():
274274
switch_prompt("sys", "Basic Example")
275275
else:
276276
# Client Settings
277-
db_alias = state.client_settings["database"]["alias"]
277+
db_alias = state.client_settings.get("database", {}).get("alias")
278278
oci_auth_profile = state.client_settings["oci"]["auth_profile"]
279279

280280
# Lookups
@@ -346,7 +346,7 @@ def _disable_vector_search(reason):
346346
#####################################################
347347
def selectai_sidebar() -> None:
348348
"""SelectAI Sidebar Settings, conditional if Database/SelectAI are configured"""
349-
db_alias = state.client_settings["database"]["alias"]
349+
db_alias = state.client_settings.get("database", {}).get("alias")
350350
database_lookup = state_configs_lookup("database_configs", "name")
351351
if state.client_settings["selectai"]["enabled"]:
352352
st.sidebar.subheader("SelectAI", divider="red")
@@ -418,22 +418,6 @@ def _render_vector_search_options(vector_search_type: str) -> None:
418418
)
419419

420420

421-
def _vs_reset() -> None:
422-
"""Reset Vector Store Selections"""
423-
for key in state.client_settings["vector_search"]:
424-
if key in (
425-
"model",
426-
"chunk_size",
427-
"chunk_overlap",
428-
"distance_metric",
429-
"vector_store",
430-
"alias",
431-
"index_type",
432-
):
433-
clear_state_key(f"selected_vector_search_{key}")
434-
state.client_settings["vector_search"][key] = ""
435-
436-
437421
def _vs_gen_selectbox(label: str, options: list, key: str):
438422
"""Handle selectbox with auto-setting for a single unique value"""
439423
valid_options = [option for option in options if option != ""]
@@ -461,7 +445,7 @@ def _vs_gen_selectbox(label: str, options: list, key: str):
461445
)
462446

463447

464-
def _update_filtered_df(vs_df: pd.DataFrame) -> pd.DataFrame:
448+
def update_filtered_vector_store(vs_df: pd.DataFrame) -> pd.DataFrame:
465449
"""Dynamically update filtered_df based on selected filters"""
466450
embed_models_enabled = enabled_models_lookup("embed")
467451
filtered = vs_df.copy()
@@ -482,17 +466,36 @@ def _update_filtered_df(vs_df: pd.DataFrame) -> pd.DataFrame:
482466
return filtered
483467

484468

485-
def _render_vector_store_selection(vs_df: pd.DataFrame) -> None:
469+
def render_vector_store_selection(vs_df: pd.DataFrame) -> None:
486470
"""Render vector store selection controls and handle state updates."""
487-
filtered_df = _update_filtered_df(vs_df)
471+
st.sidebar.subheader("Vector Store", divider="red")
472+
473+
def reset() -> None:
474+
"""Reset Vector Store Selections"""
475+
for key in state.client_settings["vector_search"]:
476+
if key in (
477+
"model",
478+
"chunk_size",
479+
"chunk_overlap",
480+
"distance_metric",
481+
"vector_store",
482+
"alias",
483+
"index_type",
484+
):
485+
clear_state_key(f"selected_vector_search_{key}")
486+
state.client_settings["vector_search"][key] = ""
487+
488+
filtered_df = update_filtered_vector_store(vs_df)
488489

489490
# Render selectbox with updated options
490491
alias = _vs_gen_selectbox("Select Alias:", filtered_df["alias"].unique().tolist(), "selected_vector_search_alias")
491492
embed_model = _vs_gen_selectbox(
492493
"Select Model:", filtered_df["model"].unique().tolist(), "selected_vector_search_model"
493494
)
494495
chunk_size = _vs_gen_selectbox(
495-
"Select Chunk Size:", filtered_df["chunk_size"].unique().tolist(), "selected_vector_search_chunk_size"
496+
"Select Chunk Size:",
497+
filtered_df["chunk_size"].unique().tolist(),
498+
"selected_vector_search_chunk_size",
496499
)
497500
chunk_overlap = _vs_gen_selectbox(
498501
"Select Chunk Overlap:",
@@ -505,7 +508,9 @@ def _render_vector_store_selection(vs_df: pd.DataFrame) -> None:
505508
"selected_vector_search_distance_metric",
506509
)
507510
index_type = _vs_gen_selectbox(
508-
"Select Index Type:", filtered_df["index_type"].unique().tolist(), "selected_vector_search_index_type"
511+
"Select Index Type:",
512+
filtered_df["index_type"].unique().tolist(),
513+
"selected_vector_search_index_type",
509514
)
510515

511516
if all([alias, embed_model, chunk_size, chunk_overlap, distance_metric, index_type]):
@@ -518,11 +523,11 @@ def _render_vector_store_selection(vs_df: pd.DataFrame) -> None:
518523
state.client_settings["vector_search"]["distance_metric"] = distance_metric
519524
state.client_settings["vector_search"]["index_type"] = index_type
520525
else:
521-
st.error("Please select Vector Store options or disable Vector Search to continue.", icon="")
526+
st.info("Please select existing Vector Store options to continue.", icon="⬅️")
522527
state.enable_client = False
523528

524529
# Reset button
525-
st.sidebar.button("Reset", type="primary", on_click=_vs_reset)
530+
st.sidebar.button("Reset", type="primary", on_click=reset)
526531

527532

528533
def vector_search_sidebar() -> None:
@@ -545,10 +550,9 @@ def vector_search_sidebar() -> None:
545550
_render_vector_search_options(vector_search_type)
546551

547552
# Vector Store Section
548-
st.sidebar.subheader("Vector Store", divider="red")
549-
db_alias = state.client_settings["database"]["alias"]
553+
db_alias = state.client_settings.get("database", {}).get("alias")
550554
database_lookup = state_configs_lookup("database_configs", "name")
551-
vs_df = pd.DataFrame(database_lookup[db_alias].get("vector_stores"))
555+
vs_df = pd.DataFrame(database_lookup.get(db_alias, {}).get("vector_stores", []))
552556

553557
# Render vector store selection controls
554-
_render_vector_store_selection(vs_df)
558+
render_vector_store_selection(vs_df)

tests/client/content/tools/tabs/test_split_embed.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,3 +692,130 @@ def test_embedding_server_not_accessible(self, app_server, app_test, monkeypatch
692692
# Should show warning about server accessibility
693693
warnings = at.get("warning")
694694
assert len(warnings) > 0
695+
696+
def test_create_new_vs_toggle_not_shown_when_no_vector_stores(self, app_server, app_test, monkeypatch):
697+
"""Test that 'Create New Vector Store' toggle is NOT shown when no vector stores exist"""
698+
assert app_server is not None
699+
self._setup_common_mocks(monkeypatch)
700+
701+
# Mock database_configs with no vector stores
702+
def mock_get_no_vs(endpoint=None, **kwargs):
703+
if endpoint == "v1/models":
704+
return [
705+
{
706+
"id": "test-model",
707+
"type": "embed",
708+
"enabled": True,
709+
"api_base": "http://test.url",
710+
"max_chunk_size": 1000,
711+
}
712+
]
713+
elif endpoint == "v1/databases":
714+
return [
715+
{
716+
"name": "DEFAULT",
717+
"vector_stores": [], # No vector stores
718+
}
719+
]
720+
elif endpoint == "v1/oci":
721+
return [
722+
{
723+
"auth_profile": "DEFAULT",
724+
"namespace": "test-namespace",
725+
"tenancy": "test-tenancy",
726+
"region": "us-ashburn-1",
727+
"authentication": "api_key",
728+
}
729+
]
730+
return {}
731+
732+
monkeypatch.setattr("client.utils.api_call.get", mock_get_no_vs)
733+
monkeypatch.setattr("common.functions.is_url_accessible", lambda api_base: (True, ""))
734+
monkeypatch.setattr("client.utils.st_common.is_db_configured", lambda: True)
735+
736+
at = self._run_app_and_verify_no_errors(app_test)
737+
738+
# Toggle should NOT be present when no vector stores exist
739+
toggles = at.get("toggle")
740+
create_new_toggle = next(
741+
(t for t in toggles if hasattr(t, "label") and "Create New Vector Store" in str(t.label)), None
742+
)
743+
assert create_new_toggle is None, "Toggle should not be shown when no vector stores exist"
744+
745+
def test_create_new_vs_toggle_shown_when_vector_stores_exist(self, app_server, app_test, monkeypatch):
746+
"""Test that 'Create New Vector Store' toggle IS shown when vector stores exist"""
747+
assert app_server is not None
748+
self._setup_common_mocks(monkeypatch)
749+
750+
# Mock database_configs with existing vector stores
751+
def mock_get_with_vs(endpoint=None, **kwargs):
752+
if endpoint == "v1/models":
753+
return [
754+
{
755+
"id": "test-model",
756+
"type": "embed",
757+
"enabled": True,
758+
"api_base": "http://test.url",
759+
"max_chunk_size": 1000,
760+
}
761+
]
762+
elif endpoint == "v1/databases":
763+
return [
764+
{
765+
"name": "DEFAULT",
766+
"vector_stores": [
767+
{
768+
"alias": "existing_vs",
769+
"model": "test-model",
770+
"vector_store": "VECTOR_STORE_TABLE",
771+
"chunk_size": 500,
772+
"chunk_overlap": 50,
773+
"distance_metric": "COSINE",
774+
"index_type": "IVF",
775+
}
776+
],
777+
}
778+
]
779+
elif endpoint == "v1/oci":
780+
return [
781+
{
782+
"auth_profile": "DEFAULT",
783+
"namespace": "test-namespace",
784+
"tenancy": "test-tenancy",
785+
"region": "us-ashburn-1",
786+
"authentication": "api_key",
787+
}
788+
]
789+
return {}
790+
791+
monkeypatch.setattr("client.utils.api_call.get", mock_get_with_vs)
792+
monkeypatch.setattr("common.functions.is_url_accessible", lambda api_base: (True, ""))
793+
monkeypatch.setattr("client.utils.st_common.is_db_configured", lambda: True)
794+
795+
at = self._run_app_and_verify_no_errors(app_test)
796+
797+
# Toggle SHOULD be present when vector stores exist
798+
toggles = at.get("toggle")
799+
create_new_toggle = next(
800+
(t for t in toggles if hasattr(t, "label") and "Create New Vector Store" in str(t.label)), None
801+
)
802+
assert create_new_toggle is not None, "Toggle should be shown when vector stores exist"
803+
assert create_new_toggle.value is True, "Toggle should default to True (create new mode)"
804+
805+
def test_populate_button_shown_in_create_new_mode(self, app_server, app_test, monkeypatch):
806+
"""Test that 'Populate Vector Store' button is shown when in create new mode"""
807+
assert app_server is not None
808+
self._setup_common_mocks(monkeypatch)
809+
810+
at = self._run_app_and_verify_no_errors(app_test)
811+
812+
# Should have Populate button
813+
buttons = at.get("button")
814+
populate_button = next(
815+
(b for b in buttons if hasattr(b, "label") and "Populate Vector Store" in str(b.label)), None
816+
)
817+
assert populate_button is not None, "Populate button should be present in create new mode"
818+
819+
# Should NOT have Refresh button when in create new mode
820+
refresh_button = next((b for b in buttons if hasattr(b, "label") and "Refresh from OCI" in str(b.label)), None)
821+
assert refresh_button is None, "Refresh button should not be present in create new mode"

0 commit comments

Comments
 (0)