Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/client/cli/commands/teams.py: 0%
124 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""Team management commands for LiteLLM CLI."""
3from collections.abc import Mapping, Sequence
4from typing import Any, Final
6import click
7import requests
8from rich.console import Console
9from rich.table import Table
10from typing_extensions import ReadOnly, TypedDict
12from litellm.proxy.client import Client
14from ._cli_context import cli_context_values
17class _TeamRow(TypedDict):
18 team_alias: ReadOnly[str | None]
19 team_id: ReadOnly[str | None]
20 models: ReadOnly[Sequence[str]]
21 max_budget: ReadOnly[object]
24class _TeamModelsView(TypedDict):
25 models: ReadOnly[Sequence[str]]
28class _ErrorBodyView(TypedDict):
29 body: ReadOnly[Mapping[str, object]]
32@click.group()
33def teams():
34 """Manage teams and team assignments"""
37def display_teams_table(teams: Sequence[dict[str, Any]]) -> None:
38 """Display teams in a formatted table"""
39 console: Final = Console()
41 if not teams:
42 console.print("No teams found for your user.")
43 return
45 table: Final = Table(title="Available Teams")
46 table.add_column("Index", style="cyan", no_wrap=True)
47 table.add_column("Team Alias", style="magenta")
48 table.add_column("Team ID", style="green")
49 table.add_column("Models", style="yellow")
50 table.add_column("Max Budget", style="blue")
51 table.add_column("Role", style="red")
53 for i, team in enumerate(teams):
54 row: _TeamRow = {
55 "team_alias": team.get("team_alias") or "N/A",
56 "team_id": team.get("team_id", "N/A"),
57 "models": team.get("models", []),
58 "max_budget": team.get("max_budget"),
59 }
60 models = row["models"]
61 max_budget = row["max_budget"]
63 # Format models list
64 if models:
65 if len(models) > 3:
66 models_str = ", ".join(models[:3]) + f" (+{len(models) - 3} more)"
67 else:
68 models_str = ", ".join(models)
69 else:
70 models_str = "All models"
72 # Format budget
73 budget_str = f"${max_budget}" if max_budget else "Unlimited"
75 # Try to determine role (this might vary based on API response structure)
76 role = "Member" # Default role
77 if isinstance(team, dict) and "members_with_roles" in team and team["members_with_roles"]:
78 # This would need to be implemented based on actual API response structure
79 pass
81 table.add_row(str(i + 1), row["team_alias"], row["team_id"], models_str, budget_str, role)
83 console.print(table)
86@teams.command()
87@click.pass_context
88def list(ctx: click.Context):
89 """List teams that you belong to"""
90 context: Final = cli_context_values(ctx)
91 client: Final = Client(context["base_url"], context["api_key"])
93 try:
94 # Use list() for simpler response structure (returns array directly)
95 teams: Final = client.teams.list()
96 display_teams_table(teams)
97 except requests.exceptions.HTTPError as e:
98 click.echo(f"Error: HTTP {e.response.status_code}", err=True)
99 error_body: Final[_ErrorBodyView] = {"body": e.response.json()}
100 click.echo(f"Details: {error_body['body'].get('detail', 'Unknown error')}", err=True)
101 raise click.Abort()
102 except Exception as e:
103 click.echo(f"Error: {e}", err=True)
104 raise click.Abort()
107@teams.command()
108@click.pass_context
109def available(ctx: click.Context):
110 """List teams that are available to join"""
111 context: Final = cli_context_values(ctx)
112 client: Final = Client(context["base_url"], context["api_key"])
114 try:
115 teams: Final = client.teams.get_available()
116 if teams:
117 console: Final = Console()
118 console.print("\nAvailable Teams to Join:")
119 display_teams_table(teams)
120 else:
121 click.echo("No available teams to join.")
122 except requests.exceptions.HTTPError as e:
123 click.echo(f"Error: HTTP {e.response.status_code}", err=True)
124 error_body: Final[_ErrorBodyView] = {"body": e.response.json()}
125 click.echo(f"Details: {error_body['body'].get('detail', 'Unknown error')}", err=True)
126 except Exception as e:
127 click.echo(f"Error: {e}", err=True)
128 raise click.Abort()
131@teams.command()
132@click.option("--team-id", type=str, help="Team ID to assign the key to")
133@click.pass_context
134def assign_key(ctx: click.Context, team_id: str | None):
135 """Assign your current CLI key to a team"""
136 context: Final = cli_context_values(ctx)
137 client: Final = Client(context["base_url"], context["api_key"])
138 api_key: Final = context["api_key"]
140 if not api_key:
141 click.echo("No API key found. Please login first using 'litellm login'")
142 raise click.Abort()
144 try:
145 # If no team_id provided, show teams and let user select
146 if not team_id:
147 teams = client.teams.list()
149 if not teams:
150 click.echo("No teams found for your user.")
151 return
153 # Use interactive selection from auth module
154 from .auth import prompt_team_selection
156 selected_team: Final = prompt_team_selection(teams)
158 if selected_team:
159 team_id = selected_team.get("team_id")
160 else:
161 click.echo("Operation cancelled.")
162 return
164 # Update the key with the selected team
165 if team_id:
166 click.echo(f"\nAssigning your key to team: {team_id}")
167 client.keys.update(key=api_key, team_id=team_id)
168 click.echo(f"Successfully assigned key to team: {team_id}")
170 # Show team details if available
171 teams = client.teams.list()
172 for team in teams:
173 if team.get("team_id") == team_id:
174 team_models: _TeamModelsView = {"models": team.get("models", [])}
175 if team_models["models"]:
176 click.echo(f"You can now access models: {', '.join(team_models['models'])}")
177 else:
178 click.echo("You can now access all available models")
179 break
181 except requests.exceptions.HTTPError as e:
182 click.echo(f"Error: HTTP {e.response.status_code}", err=True)
183 error_body: Final[_ErrorBodyView] = {"body": e.response.json()}
184 click.echo(f"Details: {error_body['body'].get('detail', 'Unknown error')}", err=True)
185 raise click.Abort()
186 except Exception as e:
187 click.echo(f"Error: {e}", err=True)
188 raise click.Abort()