Skip to content
Open
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
6 changes: 5 additions & 1 deletion clarifai/cli/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,11 @@ def delete(ctx, path, force):
prompt_msg = f"Are you sure you want to delete artifact version '{version_id}'?"
else:
prompt_msg = f"Are you sure you want to delete artifact '{parsed['artifact_id']}'?"
if not click.confirm(prompt_msg):

# Using prompt_yes_no for better automation support
from clarifai.utils.cli import prompt_yes_no

if not prompt_yes_no(prompt_msg, default=False):
click.echo("Operation cancelled")
return

Expand Down
23 changes: 20 additions & 3 deletions clarifai/cli/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def login(ctx, api_url, user_id):

# Input user_id if not supplied
if not user_id:
if not sys.stdin.isatty():
logger.error("User ID is required for login in non-interactive mode.")
raise click.Abort()
user_id = click.prompt('Enter your Clarifai user ID', type=str)

click.echo() # Blank line for readability
Expand All @@ -96,8 +99,13 @@ def login(ctx, api_url, user_id):
click.echo('\n> Verifying token...')
validate_context_auth(pat, user_id, api_url)

# Save context with default name
context_name = 'default'
# Context naming
default_context_name = 'default'
click.echo('\n> Let\'s save these credentials to a new context.')
click.echo('> You can have multiple contexts to easily switch between accounts or projects.\n')
context_name = click.prompt("Enter a name for this context", default=default_context_name)

# Save context
context = Context(
context_name,
CLARIFAI_API_BASE=api_url,
Expand All @@ -119,12 +127,17 @@ def pat_display(pat):


def input_or_default(prompt, default):
if default is not None:
logger.info(f"{prompt} [Using default: {default}]")
return default
if not sys.stdin.isatty():
raise click.Abort()
value = input(prompt)
return value if value else default


# Context management commands under config group
@config.command(aliases=['get-contexts', 'list-contexts', 'ls'])
@config.command(aliases=['get-contexts', 'list-contexts', 'ls', 'list'])
@click.option(
'-o', '--output-format', default='wide', type=click.Choice(['wide', 'name', 'json', 'yaml'])
)
Expand All @@ -138,6 +151,7 @@ def get_contexts(ctx, output_format):
'USER_ID': lambda c: c.user_id,
'API_BASE': lambda c: c.api_base,
'PAT': lambda c: pat_display(c.pat),
'HF_TOKEN': lambda c: pat_display(c.hf_token) if hasattr(c, 'hf_token') else "",
}
additional_columns = set()
for cont in ctx.obj.contexts.values():
Expand Down Expand Up @@ -213,6 +227,9 @@ def create_context(
click.secho(f'Error: Context "{name}" already exists', fg='red', err=True)
sys.exit(1)
if not user_id:
if not sys.stdin.isatty():
click.echo("Error: user-id is required in non-interactive mode.", err=True)
sys.exit(1)
user_id = input('user id: ')
if not base_url:
base_url = input_or_default(
Expand Down
97 changes: 73 additions & 24 deletions clarifai/cli/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,72 +16,121 @@ def deployment():


@deployment.command(['c'])
@click.argument('nodepool_id')
@click.argument('deployment_id')
@click.option(
'--config',
type=click.Path(exists=True),
required=True,
help='Path to the deployment config file.',
help='Path to the deployment config YAML file.',
)
@click.pass_context
def create(ctx, nodepool_id, deployment_id, config):
"""Create a new Deployment with the given config file."""
def create(ctx, config):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see wait=True parameter to nodepool.create_deployment(), we can also have --wait flag in CLI as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I think default value is True, so it's fine, but still good to have a flag

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah feel free to run with this PR and add any improvements.

"""
Create a new Deployment from a config file.

The config file is a YAML that defines the worker (model or workflow),
nodepools, autoscale settings, and visibility.

Ex: clarifai deployment create --config deployment.yaml

Example deployment.yaml:

\b
deployment:
id: "my-deployment"
worker:
model:
id: "model-id"
model_version:
id: "version-id"
user_id: "owner-id"
app_id: "app-id"
nodepools:
- id: "nodepool-id"
compute_cluster:
id: "cluster-id"
user_id: "cluster-owner-id"
autoscale_config:
min_replicas: 1
max_replicas: 1
scale_to_zero_delay_seconds: 300
deploy_latest_version: true

"""

from clarifai.client.nodepool import Nodepool

validate_context(ctx)
if not nodepool_id:
deployment_config = from_yaml(config)
nodepool_id = deployment_config['deployment']['nodepools'][0]['id']
deployment_config = from_yaml(config)
nodepool_id = deployment_config['deployment']['nodepools'][0]['id']
deployment_id = deployment_config['deployment']['id']

nodepool = Nodepool(
nodepool_id=nodepool_id,
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
if deployment_id:
nodepool.create_deployment(config, deployment_id=deployment_id)
else:
nodepool.create_deployment(config)
nodepool.create_deployment(config, deployment_id=deployment_id)


@deployment.command(['ls'])
@click.argument('nodepool_id', default="")
@click.option('--nodepool_id', required=False, help='Nodepool ID to list deployments for.')
@click.option(
'--compute_cluster_id', required=False, help='Compute cluster ID to list deployments for.'
)
@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.pass_context
def list(ctx, nodepool_id, page_no, per_page):
def list(ctx, nodepool_id, compute_cluster_id, page_no, per_page):
"""List all deployments for the nodepool."""
from clarifai_grpc.grpc.api import resources_pb2

from clarifai.client.compute_cluster import ComputeCluster
from clarifai.client.nodepool import Nodepool
from clarifai.client.user import User

validate_context(ctx)
if nodepool_id:
kwargs = {}
if compute_cluster_id:
kwargs['compute_cluster'] = resources_pb2.ComputeCluster(id=compute_cluster_id)
nodepool = Nodepool(
nodepool_id=nodepool_id,
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
**kwargs,
)
response = nodepool.list_deployments(page_no=page_no, per_page=per_page)
else:
user = User(
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
ccs = user.list_compute_clusters(page_no, per_page)
nps = []
for cc in ccs:
compute_cluster = ComputeCluster(
compute_cluster_id=cc.id,
if compute_cluster_id:
ccs = [
ComputeCluster(
compute_cluster_id=compute_cluster_id,
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
]
else:
user = User(
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
all_ccs = user.list_compute_clusters(page_no, per_page)
ccs = [
ComputeCluster(
compute_cluster_id=cc.id,
user_id=ctx.obj.current.user_id,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
for cc in all_ccs
]

nps = []
for compute_cluster in ccs:
nps.extend([i for i in compute_cluster.list_nodepools(page_no, per_page)])
response = []
for np in nps:
Expand Down
56 changes: 31 additions & 25 deletions clarifai/cli/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,6 @@ def init(
files_to_download[i] = f"{i + 1}. {file}"
files_to_download = '\n'.join(files_to_download)
logger.info(f"Files to be downloaded are:\n{files_to_download}")
input("Press Enter to continue...")
if not toolkit:
if folder_path != "":
try:
Expand Down Expand Up @@ -781,7 +780,6 @@ def init(
# Fall back to template-based initialization if no GitHub repo or if GitHub repo failed
if not github_url:
logger.info("Initializing model with default templates...")
input("Press Enter to continue...")

from clarifai.cli.base import input_or_default
from clarifai.cli.templates.model_templates import (
Expand Down Expand Up @@ -908,8 +906,14 @@ def _ensure_hf_token(ctx, model_path):
required=False,
help='Target platform(s) for Docker image build (e.g., "linux/amd64" or "linux/amd64,linux/arm64"). This overrides the platform specified in config.yaml.',
)
@click.option(
'--autodeploy',
is_flag=True,
default=False,
help='If provided, automatically walk through the creation of a deployment after uploading.',
)
@click.pass_context
def upload(ctx, model_path, stage, skip_dockerfile, platform):
def upload(ctx, model_path, stage, skip_dockerfile, platform, autodeploy):
"""Upload a model to Clarifai.

MODEL_PATH: Path to the model directory. If not specified, the current directory is used by default.
Expand All @@ -925,6 +929,7 @@ def upload(ctx, model_path, stage, skip_dockerfile, platform):
stage,
skip_dockerfile,
platform=platform,
autodeploy=autodeploy,
pat=ctx.obj.current.pat,
base_url=ctx.obj.current.api_base,
)
Expand Down Expand Up @@ -1298,10 +1303,10 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
raise
except Exception as e:
logger.warning(f"Failed to get compute cluster with ID '{compute_cluster_id}':\n{e}")
y = input(
f"Compute cluster not found. Do you want to create a new compute cluster {user_id}/{compute_cluster_id}? (y/n): "
)
if y.lower() != 'y':
if not prompt_yes_no(
f"Compute cluster not found. Do you want to create a new compute cluster {user_id}/{compute_cluster_id}?",
default=True,
):
raise click.Abort()
# Create a compute cluster with default configuration for local runner.
compute_cluster = user.create_compute_cluster(
Expand All @@ -1327,10 +1332,10 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
ctx.obj.to_yaml() # save to yaml file.
except Exception as e:
logger.warning(f"Failed to get nodepool with ID '{nodepool_id}':\n{e}")
y = input(
f"Nodepool not found. Do you want to create a new nodepool {user_id}/{compute_cluster_id}/{nodepool_id}? (y/n): "
)
if y.lower() != 'y':
if not prompt_yes_no(
f"Nodepool not found. Do you want to create a new nodepool {user_id}/{compute_cluster_id}/{nodepool_id}?",
default=True,
):
raise click.Abort()
nodepool = compute_cluster.create_nodepool(
nodepool_config=DEFAULT_LOCAL_RUNNER_NODEPOOL_CONFIG, nodepool_id=nodepool_id
Expand All @@ -1355,8 +1360,9 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
ctx.obj.to_yaml() # save to yaml file.
except Exception as e:
logger.warning(f"Failed to get app with ID '{app_id}':\n{e}")
y = input(f"App not found. Do you want to create a new app {user_id}/{app_id}? (y/n): ")
if y.lower() != 'y':
if not prompt_yes_no(
f"App not found. Do you want to create a new app {user_id}/{app_id}?", default=True
):
raise click.Abort()
app = user.create_app(app_id)
ctx.obj.current.CLARIFAI_APP_ID = app_id
Expand Down Expand Up @@ -1385,10 +1391,10 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
raise Exception
except Exception as e:
logger.warning(f"Failed to get model with ID '{model_id}':\n{e}")
y = input(
f"Model not found. Do you want to create a new model {user_id}/{app_id}/models/{model_id}? (y/n): "
)
if y.lower() != 'y':
if not prompt_yes_no(
f"Model not found. Do you want to create a new model {user_id}/{app_id}/models/{model_id}?",
default=True,
):
raise click.Abort()

model = app.create_model(model_id, model_type_id=uploaded_model_type_id)
Expand Down Expand Up @@ -1531,10 +1537,10 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
ctx.obj.to_yaml() # save to yaml file.
except Exception as e:
logger.warning(f"Failed to get deployment with ID {deployment_id}:\n{e}")
y = input(
f"Deployment not found. Do you want to create a new deployment {user_id}/{compute_cluster_id}/{nodepool_id}/{deployment_id}? (y/n): "
)
if y.lower() != 'y':
if not prompt_yes_no(
f"Deployment not found. Do you want to create a new deployment {user_id}/{compute_cluster_id}/{nodepool_id}/{deployment_id}?",
default=True,
):
raise click.Abort()
nodepool.create_deployment(
deployment_id=deployment_id,
Expand Down Expand Up @@ -1565,10 +1571,10 @@ def local_runner(ctx, model_path, pool_size, suppress_toolkit_logs, mode, keep_i
# The config.yaml doens't match what we created above.
if 'model' in config and model_id != config['model'].get('id'):
logger.info(f"Current model section of config.yaml: {config.get('model', {})}")
y = input(
"Do you want to backup config.yaml to config.yaml.bk then update the config.yaml with the new model information? (y/n): "
)
if y.lower() != 'y':
if not prompt_yes_no(
"Do you want to backup config.yaml to config.yaml.bk then update the config.yaml with the new model information?",
default=True,
):
raise click.Abort()
config = ModelBuilder._set_local_runner_model(
config, user_id, app_id, model_id, uploaded_model_type_id
Expand Down
4 changes: 3 additions & 1 deletion clarifai/cli/nodepool.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def create(ctx, compute_cluster_id, nodepool_id, config):


@nodepool.command(['ls'])
@click.argument('compute_cluster_id', default="")
@click.option(
'--compute_cluster_id', required=False, help='Compute cluster ID to list nodepools for.'
)
@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=128)
@click.pass_context
Expand Down
Loading
Loading