Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions clarifai/cli/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import shutil

import click

from clarifai.cli.base import cli
from clarifai.utils.cli import AliasedGroup, validate_context


@cli.group(
['app', 'a'],
cls=AliasedGroup,
context_settings={'max_content_width': shutil.get_terminal_size().columns - 10},
)
def app():
"""Manage Apps: create, delete, list"""


@app.command(['c'])
@click.argument('app_id')
@click.option(
'--base-workflow',
default='Empty',
help='Base workflow to use for the app. Examples: Universal, Language-Understanding, General',
)
@click.pass_context
def create(ctx, app_id, base_workflow):
"""Create a new App with the given app ID."""
from clarifai.client.user import User

validate_context(ctx)
user = User(
user_id=ctx.obj.current.user_id, pat=ctx.obj.current.pat, base_url=ctx.obj.current.api_base
)
user.create_app(app_id=app_id, base_workflow=base_workflow)
click.echo(f"App '{app_id}' created successfully.")


@app.command(['ls'])
@click.option('--page_no', required=False, help='Page number to list.', default=1)
@click.option('--per_page', required=False, help='Number of items per page.', default=16)
@click.option(
'--user_id',
required=False,
help='User ID to list apps for (defaults to current user).',
default=None,
)
@click.pass_context
def list(ctx, page_no, per_page, user_id):
"""List all apps for the user."""
from clarifai.client.user import User

validate_context(ctx)
# Use provided user_id or fall back to current context's user_id
target_user_id = user_id if user_id else ctx.obj.current.user_id
user = User(user_id=target_user_id, pat=ctx.obj.current.pat, base_url=ctx.obj.current.api_base)
apps = [app for app in user.list_apps(page_no=page_no, per_page=per_page)]

if not apps:
click.echo("No apps found.")
return

# Display apps in a simple table
from tabulate import tabulate

rows = []
for app in apps:
rows.append([app.user_id, app.id])

table = tabulate(rows, headers=["User ID", "App ID"], tablefmt="plain")
click.echo(table)


@app.command(['rm'])
@click.argument('app_id')
@click.pass_context
def delete(ctx, app_id):
"""Deletes an app for the user."""
from clarifai.client.user import User

validate_context(ctx)
user = User(
user_id=ctx.obj.current.user_id, pat=ctx.obj.current.pat, base_url=ctx.obj.current.api_base
)
user.delete_app(app_id)
click.echo(f"App '{app_id}' deleted successfully.")
24 changes: 21 additions & 3 deletions clarifai/cli/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@
from typing import Dict, Optional

import click
from clarifai_grpc.grpc.api import resources_pb2
from google.protobuf.timestamp_pb2 import Timestamp

from clarifai.cli.base import cli
from clarifai.client.artifact import Artifact
from clarifai.client.artifact_version import ArtifactVersion
from clarifai.constants.artifact import (
ARTIFACT_VISIBILITY_ORG,
ARTIFACT_VISIBILITY_PRIVATE,
Expand Down Expand Up @@ -139,6 +136,8 @@ def _upload_artifact(source_path: str, parsed_destination: dict, client_kwargs:
Returns:
ArtifactVersion: The created artifact version
"""
from clarifai.client.artifact_version import ArtifactVersion

user_id = parsed_destination['user_id']
app_id = parsed_destination['app_id']
artifact_id = parsed_destination['artifact_id'] # Now required
Expand Down Expand Up @@ -177,6 +176,10 @@ def _download_artifact(
Returns:
str: The path where file was downloaded
"""

from clarifai.client.artifact import Artifact
from clarifai.client.artifact_version import ArtifactVersion

user_id = parsed_source['user_id']
app_id = parsed_source['app_id']
artifact_id = parsed_source['artifact_id']
Expand Down Expand Up @@ -229,6 +232,12 @@ def list(ctx, path, versions):
clarifai af list users/u/apps/a
clarifai af list users/u/apps/a/artifacts/my-artifact --versions
"""

from clarifai_grpc.grpc.api import resources_pb2

from clarifai.client.artifact import Artifact
from clarifai.client.artifact_version import ArtifactVersion

try:
validate_context(ctx)

Expand Down Expand Up @@ -327,6 +336,11 @@ def get(ctx, path):
clarifai af get users/u/apps/a/artifacts/my-artifact
clarifai af get users/u/apps/a/artifacts/my-artifact/versions/v123
"""
from clarifai_grpc.grpc.api import resources_pb2

from clarifai.client.artifact import Artifact
from clarifai.client.artifact_version import ArtifactVersion

try:
validate_context(ctx)
parsed = _parse_and_validate_path(path)
Expand Down Expand Up @@ -399,6 +413,9 @@ def delete(ctx, path, force):
clarifai af rm users/u/apps/a/artifacts/my-artifact/versions/v123
clarifai af rm users/u/apps/a/artifacts/my-artifact --force
"""
from clarifai.client.artifact import Artifact
from clarifai.client.artifact_version import ArtifactVersion

try:
validate_context(ctx)
parsed = _parse_and_validate_path(path)
Expand Down Expand Up @@ -484,6 +501,7 @@ def cp(
clarifai af cp users/u/apps/a/artifacts/my-artifact/versions/v123 /tmp/
clarifai af cp users/u/apps/a/artifacts/my-artifact .
"""

try:
validate_context(ctx)

Expand Down
60 changes: 56 additions & 4 deletions clarifai/cli/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
import yaml

from clarifai import __version__
from clarifai.utils.cli import AliasedGroup, TableFormatter, load_command_modules, masked_input
from clarifai.utils.cli import (
LazyAliasedGroup,
TableFormatter,
masked_input,
)
from clarifai.utils.config import Config, Context
from clarifai.utils.constants import DEFAULT_BASE, DEFAULT_CONFIG, DEFAULT_UI
from clarifai.utils.logging import logger


@click.group(cls=AliasedGroup)
@click.group(cls=LazyAliasedGroup)
@click.version_option(version=__version__)
@click.option('--config', default=DEFAULT_CONFIG, help='Path to config file')
@click.option('--context', default=None, help='Context to use for this command')
Expand Down Expand Up @@ -58,7 +62,7 @@ def shell_completion(shell):
os.system(f"_CLARIFAI_COMPLETE={shell}_source clarifai")


@cli.group(cls=AliasedGroup)
@cli.group(cls=LazyAliasedGroup)
def config():
"""
Manage multiple configuration profiles (contexts).
Expand Down Expand Up @@ -120,6 +124,54 @@ def login(ctx, api_url, user_id):
logger.info(f"Login successful for user '{user_id}' in context '{context_name}'")


@cli.command()
@click.pass_context
def whoami(ctx):
"""Display information about the current user."""
from clarifai_grpc.grpc.api.status import status_code_pb2

from clarifai.client.user import User

# Get the current context
cfg = ctx.obj
current_ctx = cfg.contexts[cfg.current_context]

# Get user_id from context
context_user_id = current_ctx.CLARIFAI_USER_ID
pat = current_ctx.CLARIFAI_PAT
base_url = current_ctx.CLARIFAI_API_BASE

# Display context user info
click.echo("Context User ID: " + click.style(context_user_id, fg='cyan', bold=True))

# Call GetUser RPC with "me" to get the actual authenticated user
try:
user_client = User(user_id="me", pat=pat, base_url=base_url)
response = user_client.get_user_info(user_id="me")

if response.status.code == status_code_pb2.SUCCESS:
actual_user_id = response.user.id
click.echo(
"Authenticated User ID: " + click.style(actual_user_id, fg='green', bold=True)
)

# Check if they differ
if context_user_id != actual_user_id:
click.echo()
click.secho(
"��️ Warning: The context user ID differs from the authenticated user ID!",
fg='yellow',
)
click.echo(
"This means you as the caller will be calling different user or organization."
)
else:
click.secho(f"Error getting user info: {response.status.description}", fg='red')

except Exception as e:
click.secho(f"Error: Could not retrieve authenticated user info: {str(e)}", fg='red')


def _warn_env_pat():
"""Warn if CLARIFAI_PAT environment variable is still set."""
if os.environ.get('CLARIFAI_PAT'):
Expand Down Expand Up @@ -539,7 +591,7 @@ def run(ctx, script, context=None):


# Import the CLI commands to register them
load_command_modules()
# load_command_modules() - Now handled lazily by LazyLazyAliasedGroupp


def main():
Expand Down
5 changes: 4 additions & 1 deletion clarifai/cli/pipeline_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from clarifai.cli.base import cli
from clarifai.utils.cli import AliasedGroup, display_co_resources
from clarifai.utils.logging import logger
from clarifai.utils.template_manager import TemplateManager


@cli.group(
Expand Down Expand Up @@ -37,6 +36,8 @@ def list_templates(template_type):
clarifai pipelinetemplate ls --type=train # List only training templates
clarifai pipelinetemplate ls --type=data # List only data processing templates
"""
from clarifai.utils.template_manager import TemplateManager

try:
template_manager = TemplateManager()
templates = template_manager.list_templates(template_type)
Expand Down Expand Up @@ -102,6 +103,8 @@ def info(template_name):
clarifai pipelinetemplate info image-classification
clarifai pipelinetemplate info text-prep
"""
from clarifai.utils.template_manager import TemplateManager

try:
template_manager = TemplateManager()
template_info = template_manager.get_template_info(template_name)
Expand Down
43 changes: 43 additions & 0 deletions clarifai/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,49 @@ def format_commands(self, ctx, formatter):
formatter.write_dl(rows)


class LazyAliasedGroup(AliasedGroup):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lazy_mapping = {
'app': 'app',
'a': 'app',
'artifact': 'artifact',
'af': 'artifact',
'computecluster': 'compute_cluster',
'cc': 'compute_cluster',
'deployment': 'deployment',
'dp': 'deployment',
'model': 'model',
'nodepool': 'nodepool',
'np': 'nodepool',
'pipeline': 'pipeline',
'pl': 'pipeline',
'pipelinerun': 'pipeline_run',
'pr': 'pipeline_run',
'pipelinestep': 'pipeline_step',
'ps': 'pipeline_step',
'pipelinetemplate': 'pipeline_template',
'pt': 'pipeline_template',
}

def get_command(self, ctx: click.Context, cmd_name: str) -> Optional[click.Command]:
cmd = super().get_command(ctx, cmd_name)
if cmd is not None:
return cmd

if cmd_name in self.lazy_mapping:
module_name = self.lazy_mapping[cmd_name]
importlib.import_module(f'clarifai.cli.{module_name}')
return super().get_command(ctx, cmd_name)

return None

def list_commands(self, ctx: click.Context) -> t.List[str]:
base_commands = super().list_commands(ctx)
lazy_commands = [k for k in self.lazy_mapping.keys()]
return sorted(set(base_commands) | set(lazy_commands))


def validate_context(ctx):
from clarifai.utils.logging import logger

Expand Down
Loading
Loading