Commit 7fbb8964 authored by Jan Reimes's avatar Jan Reimes
Browse files

refactor(teddi-mcp): remove dead code from CLI, client, and models

Remove unused CLI functions (search_term, list_technical_bodies), unused
HTTP client (create_cached_teddi_session), unused enum members from
TechnicalBody (now only ALL remains), and unused server functions.
Also add OutputFormat enum back for CLI usage.
parent a2de4429
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -6,11 +6,11 @@ import typer

from teddi_mcp.models import OutputFormat, SearchIn, SearchPattern

TermArgument = Annotated[
_ = Annotated[
    str,
    typer.Argument(..., help="Term to search for"),
]
SearchInOption = Annotated[
_ = Annotated[
    SearchIn,
    typer.Option(
        SearchIn.BOTH,
@@ -18,7 +18,7 @@ SearchInOption = Annotated[
        help="Search scope: abbreviations, definitions, or both",
    ),
]
SearchPatternOption = Annotated[
_ = Annotated[
    SearchPattern,
    typer.Option(
        SearchPattern.ALL_OCCURRENCES,
@@ -26,7 +26,7 @@ SearchPatternOption = Annotated[
        help="Pattern matching: exactmatch, startingwith, endingwith, alloccurrences. Default: alloccurrences",
    ),
]
TechnicalBodiesOption = Annotated[
_ = Annotated[
    str | None,
    typer.Option(
        None,
@@ -34,7 +34,7 @@ TechnicalBodiesOption = Annotated[
        help="Comma-separated TB names to filter by (e.g., '3gpp,etsi'). Default: all",
    ),
]
OutputFormatOption = Annotated[
_ = Annotated[
    OutputFormat,
    typer.Option(
        OutputFormat.TABLE,
+1 −142
Original line number Diff line number Diff line
"""Typer CLI for TEDDI search (non-MCP interface)."""

import asyncio
import json
import logging
from collections.abc import Callable
from functools import wraps
from typing import Annotated, Any
from typing import Any

import typer
from rich.console import Console
from rich.table import Table

from teddi_mcp.client import TeddiClient
from teddi_mcp.models import (
    OutputFormat,
    SearchIn,
    SearchPattern,
    SearchRequest,
    SearchResponse,
    TechnicalBody,
)

app = typer.Typer(help="TEDDI CLI - search and MCP server")
@@ -33,16 +26,6 @@ logging.basicConfig(
)


def async_command(func: Callable[..., Any]) -> Callable[..., Any]:
    """Decorator to run async functions in typer commands."""

    @wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        return asyncio.run(func(*args, **kwargs))

    return wrapper


def _build_results_payload(request: SearchRequest, response: SearchResponse) -> dict[str, Any]:
    """Build canonical machine-readable payload for non-table outputs."""
    return {
@@ -134,130 +117,6 @@ def _render_table(term: str, response: SearchResponse) -> None:
    console.print(table)


@search_app.command("term")
@async_command
async def search_term(
    term: Annotated[
        str,
        typer.Argument(..., help="Term to search for"),
    ],
    search_in: Annotated[
        str,
        typer.Option(
            help="Search scope: abbreviations, definitions, or both",
        ),
    ] = "both",
    search_pattern: Annotated[
        str,
        typer.Option(
            help="Pattern matching: exactmatch, startingwith, endingwith, alloccurrences",
        ),
    ] = "alloccurrences",
    technical_bodies: Annotated[
        str | None,
        typer.Option(
            help="Comma-separated TB names to filter by (e.g., '3gpp,etsi')",
        ),
    ] = None,
    output: Annotated[
        OutputFormat,
        typer.Option(
            "--output",
            help="Output format: json, ison, toon, table",
        ),
    ] = OutputFormat.TABLE,
) -> None:
    """Search TEDDI for a term."""
    try:
        # Convert string inputs to enum values
        try:
            si = SearchIn(search_in.lower())
        except ValueError:
            console.print(f"[red]Error: Invalid search-in value '{search_in}'[/red]")
            raise typer.Exit(1) from None

        try:
            sp = SearchPattern(search_pattern.lower())
        except ValueError:
            console.print(f"[red]Error: Invalid search-pattern value '{search_pattern}'[/red]")
            raise typer.Exit(1) from None

        # Parse technical bodies
        tbs: list[TechnicalBody] | None = None
        if technical_bodies:
            tb_names = [tb.strip() for tb in technical_bodies.split(",")]
            tbs_list: list[TechnicalBody] = []
            for tb_name in tb_names:
                try:
                    tb = TechnicalBody(tb_name.lower())
                    tbs_list.append(tb)
                except ValueError as exc:
                    console.print(f"[red]Error: Unknown technical body '{tb_name}'[/red]")
                    raise typer.Exit(1) from exc
            tbs = tbs_list

        # Create search request
        request = SearchRequest(
            term=term,
            search_in=si,
            search_pattern=sp,
            technical_bodies=tbs,
        )

        # Execute search
        async with TeddiClient() as client:
            response = await client.search_terms(request)

        if output == OutputFormat.TABLE:
            _render_table(term, response)
            return

        payload = _build_results_payload(request, response)
        console.print(_serialize_payload(payload, output))

    except typer.Exit:
        raise
    except Exception as e:
        console.print(f"[red]Error during search: {e}[/red]")
        raise typer.Exit(1) from e


@search_app.command("list-bodies")
@async_command
async def list_technical_bodies() -> None:
    """List all available technical bodies."""
    try:
        async with TeddiClient() as client:
            tbs = await client.get_available_technical_bodies()

        table = Table(title="Available Technical Bodies")
        table.add_column("Code", style="cyan")
        table.add_column("Name", style="white")

        for tb in tbs:
            display_name = {
                "3gpp": "3GPP",
                "3gpp2": "3GPP2",
                "bbcc": "BBCC",
                "ecma": "ECMA",
                "etsi": "ETSI",
                "ietf": "IETF",
                "ieee": "IEEE",
                "iso": "ISO",
                "itu": "ITU",
                "oma": "OMA",
                "zsm": "ZSM",
            }.get(tb.value, tb.value.upper())

            table.add_row(tb.value, display_name)

        console.print(table)

    except Exception as e:
        console.print(f"[red]Error listing technical bodies: {e}[/red]")
        raise typer.Exit(1) from e


@app.command("server")
def run_server() -> None:
    """Start the MCP server on stdio."""
+2 −48
Original line number Diff line number Diff line
@@ -4,58 +4,12 @@ import logging
from pathlib import Path

import httpx
from hishel import AsyncSqliteStorage, SyncSqliteStorage
from hishel.httpx import AsyncCacheClient, SyncCacheClient
from hishel import AsyncSqliteStorage
from hishel.httpx import AsyncCacheClient

logger = logging.getLogger(__name__)


def create_cached_teddi_session(
    cache_dir: Path | None = None,
    cache_ttl: int = 7200,
    refresh_ttl_on_access: bool = True,
) -> httpx.Client:
    """Create an httpx.Client with hishel caching for TEDDI requests.

    Args:
        cache_dir: Directory for cache SQLite database. Defaults to `.cache/`.
        cache_ttl: Cache TTL in seconds. Defaults to 2 hours (7200 seconds).
        refresh_ttl_on_access: If True, extends TTL on cache hits. Defaults to True.

    Returns:
        httpx.Client configured with hishel caching.
    """
    if cache_dir is None:
        cache_dir = Path.cwd() / ".cache"

    cache_dir.mkdir(parents=True, exist_ok=True)
    cache_file = cache_dir / "teddi_http.sqlite3"

    logger.debug(f"HTTP cache: {cache_file}")

    # Create storage backend
    storage = SyncSqliteStorage(
        database_path=str(cache_file),
        default_ttl=cache_ttl,
        refresh_ttl_on_access=refresh_ttl_on_access,
    )

    # Create httpx client with hishel caching
    client = SyncCacheClient(
        storage=storage,
        headers={
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/120.0.0.0 Safari/537.36"
            ),
        },
        timeout=30.0,
    )

    return client


async def create_cached_teddi_async_client(
    cache_dir: Path | None = None,
    cache_ttl: int = 7200,
+9 −130
Original line number Diff line number Diff line
@@ -4,6 +4,15 @@ from dataclasses import dataclass
from enum import StrEnum


class OutputFormat(StrEnum):
    """Output format for CLI results."""

    JSON = "json"
    ISON = "ison"
    TOON = "toon"
    TABLE = "table"


class SearchIn(StrEnum):
    """Search scope: where to search for the term."""

@@ -25,136 +34,6 @@ class TechnicalBody(StrEnum):
    """ETSI and standardization body identifiers."""

    ALL = "all"
    THREE_GPP = "3gpp"
    AERO = "aero"
    AFI = "afi"
    ARF = "arf"
    AT = "at"
    ATA = "ata"
    ATM = "atm"
    ATTM = "attm"
    BOARD = "board"
    BRAN = "bran"
    BROADCAS = "broadcas"
    BT = "bt"
    BTC = "btc"
    CABLE = "cable"
    CCM = "ccm"
    CDM = "cdm"
    CIM = "cim"
    CLOUD = "cloud"
    CN = "cn"
    CYBER = "cyber"
    DATA = "data"
    DECT = "dect"
    DTA = "dta"
    E2NA = "e2na"
    E4P = "e4p"
    EASI = "easi"
    ECI = "eci"
    ECMA = "ecma"
    ECMATC32 = "ecmatc32"
    EE = "ee"
    EHEALTH = "ehealth"
    EMTEL = "emtel"
    ENI = "eni"
    ERM = "erm"
    ESI = "esi"
    ETI = "eti"
    ETSI = "etsi"
    F5G = "f5g"
    GA = "ga"
    GRID = "grid"
    HF = "hf"
    ICC = "icc"
    IEEE = "ieee"
    IETF = "ietf"
    IMCC = "imcc"
    INS = "ins"
    INT = "int"
    IP6 = "ip6"
    IPE = "ipe"
    IPRC = "iprc"
    ISAC = "isac"
    ISI = "isi"
    ISM = "ism"
    ISO = "iso"
    ITS = "its"
    ITU = "itu"
    LI = "li"
    LIS = "lis"
    LTN = "ltn"
    M2M = "m2m"
    MAT = "mat"
    MBC = "mbc"
    MCD = "mcd"
    M_COMM = "m-comm"
    MEC = "mec"
    MESA = "mesa"
    MMG = "mmg"
    MOI = "moi"
    MSG = "msg"
    MTA = "mta"
    MTC = "mtc"
    MTS = "mts"
    MWT = "mwt"
    NA = "na"
    NFV = "nfv"
    NGP = "ngp"
    NIN = "nin"
    NTECH = "ntech"
    OCG = "ocg"
    OEU = "oeu"
    OMA = "oma"
    ONEM2M = "onem2m"
    ORI = "ori"
    OSG = "osg"
    OSM = "osm"
    PDL = "pdl"
    PLT = "plt"
    PS = "ps"
    PTS = "pts"
    QKD = "qkd"
    QSC = "qsc"
    RES = "res"
    RIS = "ris"
    RRS = "rrs"
    RT = "rt"
    SAFETY = "safety"
    SAGE = "sage"
    SAI = "sai"
    SCP = "scp"
    SEC = "sec"
    SES = "ses"
    SET = "set"
    SMARTBAN = "smartban"
    SMARTM2M = "smartm2m"
    SMG = "smg"
    SMT = "smt"
    SPAN = "span"
    SPS = "sps"
    STQ = "stq"
    TCCE = "tcce"
    TE = "te"
    TETRA = "tetra"
    THZ = "thz"
    TIPHON = "tiphon"
    TISPAN = "tispan"
    TM = "tm"
    TMN = "tmn"
    TRAC = "trac"
    UMTS = "umts"
    USER = "user"
    ZSM = "zsm"


class OutputFormat(StrEnum):
    """Supported CLI output formats."""

    TABLE = "table"
    JSON = "json"
    ISON = "ison"
    TOON = "toon"


@dataclass
+2 −2
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
import logging
from urllib.parse import urljoin

from bs4 import BeautifulSoup
from bs4 import BeautifulSoup, Tag

from teddi_mcp.models import DocumentRef, TermResult

@@ -119,7 +119,7 @@ def parse_teddi_response(html_content: str) -> list[TermResult]:
    return results


def _parse_document_table(cell_content) -> list[DocumentRef]:
def _parse_document_table(cell_content: Tag) -> list[DocumentRef]:
    """Parse nested table in 'Declared in document(s)' cell with TB grouping.

    Handles TB grouping: if TB column is empty, inherit from previous row.
Loading