Source code for webinterface.pages.utils.module_registry
"""
Module registry for discovering and loading ProteoBench module metadata.
This module dynamically imports all pages_variables files to extract
module metadata for the sidebar navigation system.
"""
import importlib
import inspect
from dataclasses import dataclass, is_dataclass
from pathlib import Path
from typing import Dict, List, Optional
import streamlit as st
[docs]
@st.cache_resource
def get_all_modules() -> Dict[str, List[ModuleMetadata]]:
"""
Discover and load all module metadata from pages_variables.
Automatically discovers all *_variables.py files in the pages directory
and loads their dataclass definitions without requiring manual mapping.
Returns
-------
Dict[str, List[ModuleMetadata]]
Dictionary mapping categories ("DDA", "DIA", "Archived") to lists of ModuleMetadata.
"""
modules_by_category = {"DDA": [], "DIA": [], "Archived": []}
# Get the path to the pages_variables directory
# Assuming this file is in webinterface/pages/utils/
current_file = Path(__file__)
pages_variables_dir = current_file.parent.parent / "pages_variables"
# Find all *_variables.py files in all subdirectories
variable_files = sorted(pages_variables_dir.rglob("*_variables.py"))
for variable_file in variable_files:
# Skip __init__.py and other special files
if variable_file.name.startswith("_"):
continue
# Get the relative path from pages_variables to construct import path
relative_path = variable_file.relative_to(pages_variables_dir)
# Convert path to module notation: Quant/lfq_DDA_ion_QExactive_variables.py -> Quant.lfq_DDA_ion_QExactive_variables
module_parts = list(relative_path.parts[:-1]) + [relative_path.stem]
module_path = ".".join(module_parts)
try:
# Import the module
module = importlib.import_module(f"pages.pages_variables.{module_path}")
# Find the dataclass in the module
# Look for classes that are dataclasses and have the required sidebar attributes
variables_class = None
for name, obj in inspect.getmembers(module, inspect.isclass):
if is_dataclass(obj) and hasattr(obj, "__annotations__") and "sidebar_label" in obj.__annotations__:
variables_class = obj
break
if not variables_class:
print(f"Warning: No suitable dataclass found in {module_path}")
continue
# Instantiate the class
variables = variables_class()
# Determine release stage from warning flags
if hasattr(variables, "archived_warning") and variables.archived_warning:
release_stage = "archived"
elif hasattr(variables, "alpha_warning") and variables.alpha_warning:
release_stage = "alpha"
elif hasattr(variables, "beta_warning") and variables.beta_warning:
release_stage = "beta"
else:
release_stage = "live"
# Find the actual page file path
page_name = variables.sidebar_path.lstrip("/")
pages_dir = current_file.parent.parent # pages/ directory
matching_files = list(pages_dir.glob(f"*{page_name}.py"))
if matching_files:
# Get relative path from webinterface/ directory
webinterface_dir = pages_dir.parent
file_path = str(matching_files[0].relative_to(webinterface_dir))
else:
# Fallback
file_path = f"pages/{page_name}.py"
# Create metadata object
metadata = ModuleMetadata(
label=variables.sidebar_label,
path=variables.sidebar_path,
file_path=file_path,
category=variables.sidebar_category,
release_stage=release_stage,
keywords=variables.keywords,
title=variables.title,
doc_url=variables.doc_url,
)
# Add to appropriate category
if metadata.category in modules_by_category:
modules_by_category[metadata.category].append(metadata)
except Exception as e:
# Silently skip modules that fail to load
# In production, you might want to log this
print(f"Warning: Failed to load module {module_path}: {e}")
continue
return modules_by_category
[docs]
def filter_modules(
modules_by_category: Dict[str, List[ModuleMetadata]], search_query: str
) -> Dict[str, List[ModuleMetadata]]:
"""
Filter modules based on search query matching keywords or title.
Parameters
----------
modules_by_category : Dict[str, List[ModuleMetadata]]
Dictionary of modules organized by category.
search_query : str
Search query string (case-insensitive).
Returns
-------
Dict[str, List[ModuleMetadata]]
Filtered dictionary of modules matching the search query.
"""
if not search_query:
return modules_by_category
query_lower = search_query.lower()
filtered = {"DDA": [], "DIA": [], "Archived": []}
for category, modules in modules_by_category.items():
for module in modules:
# Check if query matches label, title, or any keyword
if (
query_lower in module.label.lower()
or query_lower in module.title.lower()
or any(query_lower in keyword.lower() for keyword in module.keywords)
):
filtered[category].append(module)
return filtered