Skip to content

Commit 78a5a76

Browse files
Add users/teams/tags commands, remove deprecated root commands
Implement basic users (list, get), teams (list, get), and tags (list, get, create, delete) command groups with full API client modules. Remove deprecated qualytics init and qualytics show-config forwarding commands for V1 cleanup. Verify checks activate is properly visible.
1 parent f6a5983 commit 78a5a76

10 files changed

Lines changed: 422 additions & 52 deletions

File tree

qualytics/api/tags.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Tag API operations using the centralized client."""
2+
3+
from ..api.client import QualyticsClient
4+
5+
6+
def get_tag(client: QualyticsClient, tag_id: int) -> dict:
7+
"""Get a single tag by ID."""
8+
response = client.get(f"tags/{tag_id}")
9+
return response.json()
10+
11+
12+
def list_tags(
13+
client: QualyticsClient,
14+
*,
15+
page: int = 1,
16+
size: int = 100,
17+
) -> dict:
18+
"""List tags with pagination.
19+
20+
Returns the raw paginated response: {items, total, page, size}.
21+
"""
22+
params: dict = {"page": page, "size": size}
23+
response = client.get("tags", params=params)
24+
return response.json()
25+
26+
27+
def list_all_tags(client: QualyticsClient) -> list[dict]:
28+
"""Fetch ALL tags across all pages."""
29+
page = 1
30+
size = 100
31+
all_tags: list[dict] = []
32+
33+
while True:
34+
data = list_tags(client, page=page, size=size)
35+
items = data.get("items", [])
36+
all_tags.extend(items)
37+
total = data.get("total", 0)
38+
if page * size >= total:
39+
break
40+
page += 1
41+
42+
return all_tags
43+
44+
45+
def create_tag(client: QualyticsClient, payload: dict) -> dict:
46+
"""Create a new tag. Returns the created tag with ID."""
47+
response = client.post("tags", json=payload)
48+
return response.json()
49+
50+
51+
def delete_tag(client: QualyticsClient, tag_id: int) -> dict:
52+
"""Delete a tag by ID."""
53+
response = client.delete(f"tags/{tag_id}")
54+
if not response.content or response.status_code == 204:
55+
return {"success": True, "message": "Tag deleted successfully"}
56+
return response.json()

qualytics/api/teams.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Team API operations using the centralized client."""
2+
3+
from ..api.client import QualyticsClient
4+
5+
6+
def get_team(client: QualyticsClient, team_id: int) -> dict:
7+
"""Get a single team by ID."""
8+
response = client.get(f"teams/{team_id}")
9+
return response.json()
10+
11+
12+
def list_teams(
13+
client: QualyticsClient,
14+
*,
15+
page: int = 1,
16+
size: int = 100,
17+
) -> dict:
18+
"""List teams with pagination.
19+
20+
Returns the raw paginated response: {items, total, page, size}.
21+
"""
22+
params: dict = {"page": page, "size": size}
23+
response = client.get("teams", params=params)
24+
return response.json()
25+
26+
27+
def list_all_teams(client: QualyticsClient) -> list[dict]:
28+
"""Fetch ALL teams across all pages."""
29+
page = 1
30+
size = 100
31+
all_teams: list[dict] = []
32+
33+
while True:
34+
data = list_teams(client, page=page, size=size)
35+
items = data.get("items", [])
36+
all_teams.extend(items)
37+
total = data.get("total", 0)
38+
if page * size >= total:
39+
break
40+
page += 1
41+
42+
return all_teams

qualytics/api/users.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""User API operations using the centralized client."""
2+
3+
from ..api.client import QualyticsClient
4+
5+
6+
def get_user(client: QualyticsClient, user_id: int) -> dict:
7+
"""Get a single user by ID."""
8+
response = client.get(f"users/{user_id}")
9+
return response.json()
10+
11+
12+
def list_users(
13+
client: QualyticsClient,
14+
*,
15+
page: int = 1,
16+
size: int = 100,
17+
) -> dict:
18+
"""List users with pagination.
19+
20+
Returns the raw paginated response: {items, total, page, size}.
21+
"""
22+
params: dict = {"page": page, "size": size}
23+
response = client.get("users", params=params)
24+
return response.json()
25+
26+
27+
def list_all_users(client: QualyticsClient) -> list[dict]:
28+
"""Fetch ALL users across all pages."""
29+
page = 1
30+
size = 100
31+
all_users: list[dict] = []
32+
33+
while True:
34+
data = list_users(client, page=page, size=size)
35+
items = data.get("items", [])
36+
all_users.extend(items)
37+
total = data.get("total", 0)
38+
if page * size >= total:
39+
break
40+
page += 1
41+
42+
return all_users

qualytics/cli/main.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,37 +45,3 @@ def version_callback(
4545

4646
print("\nRun [bold]'qualytics --help'[/bold] for more details.\n")
4747
raise typer.Exit()
48-
49-
50-
@app.command(hidden=True, deprecated=True)
51-
def show_config():
52-
"""[Deprecated] Use 'qualytics auth status' instead."""
53-
print(
54-
"[bold yellow]Warning: 'qualytics show-config' is deprecated. "
55-
"Use 'qualytics auth status' instead.[/bold yellow]\n"
56-
)
57-
from .auth import auth_status
58-
59-
auth_status()
60-
61-
62-
@app.command(hidden=True, deprecated=True)
63-
def init(
64-
url: str = typer.Option(
65-
..., help="The URL to be set. Example: https://your-qualytics.qualytics.io/"
66-
),
67-
token: str = typer.Option(..., help="The token to be set."),
68-
no_verify_ssl: bool = typer.Option(
69-
False,
70-
"--no-verify-ssl",
71-
help="Disable SSL certificate verification for API requests.",
72-
),
73-
):
74-
"""[Deprecated] Use 'qualytics auth init' instead."""
75-
print(
76-
"[bold yellow]Warning: 'qualytics init' is deprecated. "
77-
"Use 'qualytics auth init' instead.[/bold yellow]\n"
78-
)
79-
from .auth import auth_init
80-
81-
auth_init(url=url, token=token, no_verify_ssl=no_verify_ssl)

qualytics/cli/tags.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""CLI commands for tag management."""
2+
3+
import typer
4+
from rich import print
5+
6+
from ..api.client import get_client, QualyticsAPIError
7+
from ..api.tags import create_tag, delete_tag, get_tag, list_all_tags
8+
from ..utils.serialization import OutputFormat, format_for_display
9+
10+
from . import add_suggestion_callback
11+
from .progress import status
12+
13+
tags_app = typer.Typer(
14+
name="tags",
15+
help="Manage tags",
16+
)
17+
add_suggestion_callback(tags_app, "tags")
18+
19+
20+
@tags_app.command("list")
21+
def tags_list(
22+
fmt: OutputFormat = typer.Option(
23+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
24+
),
25+
):
26+
"""List all tags."""
27+
client = get_client()
28+
29+
with status("[bold cyan]Fetching tags...[/bold cyan]"):
30+
all_tags = list_all_tags(client)
31+
32+
if not all_tags:
33+
print("[yellow]No tags found.[/yellow]")
34+
raise typer.Exit()
35+
36+
print(f"[bold]Found {len(all_tags)} tag(s).[/bold]\n")
37+
print(format_for_display(all_tags, fmt))
38+
39+
40+
@tags_app.command("get")
41+
def tags_get(
42+
tag_id: int = typer.Option(..., "--id", help="Tag ID"),
43+
fmt: OutputFormat = typer.Option(
44+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
45+
),
46+
):
47+
"""Get a single tag by ID."""
48+
client = get_client()
49+
50+
with status("[bold cyan]Fetching tag...[/bold cyan]"):
51+
result = get_tag(client, tag_id)
52+
53+
print(format_for_display(result, fmt))
54+
55+
56+
@tags_app.command("create")
57+
def tags_create(
58+
name: str = typer.Option(..., "--name", "-n", help="Tag name"),
59+
fmt: OutputFormat = typer.Option(
60+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
61+
),
62+
):
63+
"""Create a new tag."""
64+
client = get_client()
65+
payload = {"name": name}
66+
67+
result = create_tag(client, payload)
68+
print(f"[green]Tag created successfully![/green]")
69+
print(f"[green]Tag ID: {result.get('id')}[/green]\n")
70+
print(format_for_display(result, fmt))
71+
72+
73+
@tags_app.command("delete")
74+
def tags_delete(
75+
tag_id: int = typer.Option(..., "--id", help="Tag ID to delete"),
76+
):
77+
"""Delete a tag by ID."""
78+
client = get_client()
79+
80+
try:
81+
delete_tag(client, tag_id)
82+
print(f"[green]Tag {tag_id} deleted successfully.[/green]")
83+
except QualyticsAPIError as e:
84+
print(f"[red]Failed to delete tag {tag_id}: {e.message}[/red]")
85+
raise typer.Exit(code=1)

qualytics/cli/teams.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""CLI commands for team management."""
2+
3+
import typer
4+
from rich import print
5+
6+
from ..api.client import get_client
7+
from ..api.teams import get_team, list_all_teams
8+
from ..utils.serialization import OutputFormat, format_for_display
9+
10+
from . import add_suggestion_callback
11+
from .progress import status
12+
13+
teams_app = typer.Typer(
14+
name="teams",
15+
help="List and view teams",
16+
)
17+
add_suggestion_callback(teams_app, "teams")
18+
19+
20+
@teams_app.command("list")
21+
def teams_list(
22+
fmt: OutputFormat = typer.Option(
23+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
24+
),
25+
):
26+
"""List all teams."""
27+
client = get_client()
28+
29+
with status("[bold cyan]Fetching teams...[/bold cyan]"):
30+
all_teams = list_all_teams(client)
31+
32+
if not all_teams:
33+
print("[yellow]No teams found.[/yellow]")
34+
raise typer.Exit()
35+
36+
print(f"[bold]Found {len(all_teams)} team(s).[/bold]\n")
37+
print(format_for_display(all_teams, fmt))
38+
39+
40+
@teams_app.command("get")
41+
def teams_get(
42+
team_id: int = typer.Option(..., "--id", help="Team ID"),
43+
fmt: OutputFormat = typer.Option(
44+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
45+
),
46+
):
47+
"""Get a single team by ID."""
48+
client = get_client()
49+
50+
with status("[bold cyan]Fetching team...[/bold cyan]"):
51+
result = get_team(client, team_id)
52+
53+
print(format_for_display(result, fmt))

qualytics/cli/users.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""CLI commands for user management."""
2+
3+
import typer
4+
from rich import print
5+
6+
from ..api.client import get_client
7+
from ..api.users import get_user, list_all_users
8+
from ..utils.serialization import OutputFormat, format_for_display
9+
10+
from . import add_suggestion_callback
11+
from .progress import status
12+
13+
users_app = typer.Typer(
14+
name="users",
15+
help="List and view users",
16+
)
17+
add_suggestion_callback(users_app, "users")
18+
19+
20+
@users_app.command("list")
21+
def users_list(
22+
fmt: OutputFormat = typer.Option(
23+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
24+
),
25+
):
26+
"""List all users."""
27+
client = get_client()
28+
29+
with status("[bold cyan]Fetching users...[/bold cyan]"):
30+
all_users = list_all_users(client)
31+
32+
if not all_users:
33+
print("[yellow]No users found.[/yellow]")
34+
raise typer.Exit()
35+
36+
print(f"[bold]Found {len(all_users)} user(s).[/bold]\n")
37+
print(format_for_display(all_users, fmt))
38+
39+
40+
@users_app.command("get")
41+
def users_get(
42+
user_id: int = typer.Option(..., "--id", help="User ID"),
43+
fmt: OutputFormat = typer.Option(
44+
OutputFormat.YAML, "--format", "-f", help="Output format: yaml or json"
45+
),
46+
):
47+
"""Get a single user by ID."""
48+
client = get_client()
49+
50+
with status("[bold cyan]Fetching user...[/bold cyan]"):
51+
result = get_user(client, user_id)
52+
53+
print(format_for_display(result, fmt))

qualytics/qualytics.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
from .cli.export_import import export_import_app
2020
from .cli.mcp_cmd import mcp_app
2121
from .cli.doctor import doctor
22+
from .cli.users import users_app
23+
from .cli.teams import teams_app
24+
from .cli.tags import tags_app
2225

2326
# Import config for environment setup
2427
from .config import DOTENV_PATH
@@ -39,6 +42,9 @@
3942
app.add_typer(auth_app, name="auth")
4043
app.add_typer(export_import_app, name="config")
4144
app.add_typer(mcp_app, name="mcp")
45+
app.add_typer(users_app, name="users")
46+
app.add_typer(teams_app, name="teams")
47+
app.add_typer(tags_app, name="tags")
4248
app.command("doctor", help="Check CLI health and connectivity")(doctor)
4349

4450

0 commit comments

Comments
 (0)