Skip to content

Commit 59154f4

Browse files
luv-bansalsrikanthbachala20
authored andcommitted
[PR-739] [PR-740] Unify Context Management Under a Single config Command (#709)
* Unify Context Management Under a Single context Command * login func * improvemensts and fix tests * better alias * active to current * Unify config * Update readme * fix test * update readme * update readme * update readme * add set-context alias
1 parent cbed4cb commit 59154f4

File tree

2 files changed

+146
-118
lines changed

2 files changed

+146
-118
lines changed

clarifai/cli/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,45 @@ Clarifai offers a user-friendly interface for deploying your local model into pr
88
* Easy implementation and testing in Python
99
* No need for MLops expertise.
1010

11+
## Context Management
12+
13+
Manage CLI contexts for authentication and environment configuration:
14+
### List all contexts
15+
```bash
16+
clarifai config get-contexts
17+
```
18+
19+
### Switch context
20+
```bash
21+
clarifai config use-context production
22+
```
23+
### Show current context
24+
```bash
25+
clarifai config current-context
26+
```
27+
28+
### Create new context
29+
```bash
30+
clarifai config create-context staging --user-id myuser --pat 678***
31+
```
32+
### View entire configuration
33+
```bash
34+
clarifai config view
35+
```
36+
### Delete a context
37+
```bash
38+
clarifai config delete-context old-context
39+
```
40+
### Edit configuration file
41+
```bash
42+
clarifai config edit
43+
```
44+
45+
### Print environment variables for the active context
46+
```bash
47+
clarifai context env
48+
```
49+
1150
## Compute Orchestration
1251

1352
Quick example for deploying a `visual-classifier` model

clarifai/cli/base.py

Lines changed: 107 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import json
22
import os
3-
import shutil
43
import sys
54

65
import click
@@ -13,7 +12,6 @@
1312
from clarifai.utils.logging import logger
1413

1514

16-
# @click.group(cls=CustomMultiGroup)
1715
@click.group(cls=AliasedGroup)
1816
@click.version_option(version=__version__)
1917
@click.option('--config', default=DEFAULT_CONFIG)
@@ -54,42 +52,71 @@ def shell_completion(shell):
5452
os.system(f"_CLARIFAI_COMPLETE={shell}_source clarifai")
5553

5654

57-
@cli.group(
58-
['cfg'],
59-
cls=AliasedGroup,
60-
context_settings={'max_content_width': shutil.get_terminal_size().columns - 10},
61-
)
55+
@cli.group(cls=AliasedGroup)
6256
def config():
63-
"""Manage CLI configuration"""
57+
"""
58+
Manage multiple configuration profiles (contexts).
59+
60+
Authentication Precedence:\n
61+
1. Environment variables (e.g., `CLARIFAI_PAT`) are used first if set.
62+
2. The settings from the active context are used if no environment variables are provided.\n
63+
"""
6464

6565

66-
@config.command(['e'])
66+
@cli.command()
67+
@click.argument('api_url', default=DEFAULT_BASE)
68+
@click.option('--user_id', required=False, help='User ID')
6769
@click.pass_context
68-
def edit(ctx):
69-
"""Edit the configuration file"""
70-
os.system(f'{os.environ.get("EDITOR", "vi")} {ctx.obj.filename}')
70+
def login(ctx, api_url, user_id):
71+
"""Login command to set PAT and other configurations."""
72+
from clarifai.utils.cli import validate_context_auth
7173

74+
name = input('context name (default: "default"): ')
75+
user_id = user_id if user_id is not None else input('user id: ')
76+
pat = input_or_default(
77+
'personal access token value (default: "ENVVAR" to get out of env var rather than config): ',
78+
'ENVVAR',
79+
)
7280

73-
@config.command(['current'])
74-
@click.option('-o', '--output-format', default='name', type=click.Choice(['name', 'json', 'yaml']))
75-
@click.pass_context
76-
def current_context(ctx, output_format):
77-
"""Get the current context"""
78-
if output_format == 'name':
79-
print(ctx.obj.current_context)
80-
elif output_format == 'json':
81-
print(json.dumps(ctx.obj.contexts[ctx.obj.current_context].to_serializable_dict()))
82-
else:
83-
print(yaml.safe_dump(ctx.obj.contexts[ctx.obj.current_context].to_serializable_dict()))
81+
# Validate the Context Credentials
82+
validate_context_auth(pat, user_id, api_url)
83+
84+
context = Context(
85+
name,
86+
CLARIFAI_API_BASE=api_url,
87+
CLARIFAI_USER_ID=user_id,
88+
CLARIFAI_PAT=pat,
89+
)
90+
91+
if context.name == '':
92+
context.name = 'default'
93+
94+
ctx.obj.contexts[context.name] = context
95+
ctx.obj.current_context = context.name
96+
97+
ctx.obj.to_yaml()
98+
logger.info(
99+
f"Login successful and Configuration saved successfully for context '{context.name}'"
100+
)
101+
102+
103+
def pat_display(pat):
104+
return pat[:5] + "****"
105+
106+
107+
def input_or_default(prompt, default):
108+
value = input(prompt)
109+
return value if value else default
84110

85111

86-
@config.command(['list', 'ls'])
112+
# Context management commands under config group
113+
@config.command(aliases=['get-contexts', 'list-contexts'])
87114
@click.option(
88115
'-o', '--output-format', default='wide', type=click.Choice(['wide', 'name', 'json', 'yaml'])
89116
)
90117
@click.pass_context
91118
def get_contexts(ctx, output_format):
92-
"""Get all contexts"""
119+
"""List all available contexts."""
93120
if output_format == 'wide':
94121
columns = {
95122
'': lambda c: '*' if c.name == ctx.obj.current_context else '',
@@ -106,7 +133,6 @@ def get_contexts(ctx, output_format):
106133
additional_columns.add(key)
107134
for key in sorted(additional_columns):
108135
columns[key] = lambda c, k=key: getattr(c, k) if hasattr(c, k) else ""
109-
110136
formatter = TableFormatter(
111137
custom_columns=columns,
112138
)
@@ -123,101 +149,45 @@ def get_contexts(ctx, output_format):
123149
print(yaml.safe_dump(dicts))
124150

125151

126-
@config.command(['use'])
127-
@click.argument('context-name', type=str)
152+
@config.command(aliases=['use-context'])
153+
@click.argument('name', type=str)
128154
@click.pass_context
129-
def use_context(ctx, context_name):
130-
"""Set the current context"""
131-
if context_name not in ctx.obj.contexts:
155+
def use_context(ctx, name):
156+
"""Set the current context."""
157+
if name not in ctx.obj.contexts:
132158
raise click.UsageError('Context not found')
133-
ctx.obj.current_context = context_name
159+
ctx.obj.current_context = name
134160
ctx.obj.to_yaml()
135-
print(f'Set {context_name} as the current context')
136-
137-
138-
@config.command(['cat'])
139-
@click.option('-o', '--output-format', default='yaml', type=click.Choice(['yaml', 'json']))
140-
@click.pass_obj
141-
def dump(ctx_obj, output_format):
142-
"""Dump the configuration to stdout"""
143-
if output_format == 'yaml':
144-
yaml.safe_dump(ctx_obj.to_dict(), sys.stdout)
145-
else:
146-
json.dump(ctx_obj.to_dict(), sys.stdout, indent=2)
147-
148-
149-
@config.command(['cat'])
150-
@click.pass_obj
151-
def env(ctx_obj):
152-
"""Print env vars. Use: eval "$(clarifai config env)" """
153-
ctx_obj.current.print_env_vars()
161+
print(f'Set {name} as the current context')
154162

155163

156-
@cli.command()
157-
@click.argument('api_url', default=DEFAULT_BASE)
158-
@click.option('--user_id', required=False, help='User ID')
164+
@config.command(aliases=['current-context'])
165+
@click.option('-o', '--output-format', default='name', type=click.Choice(['name', 'json', 'yaml']))
159166
@click.pass_context
160-
def login(ctx, api_url, user_id):
161-
"""Login command to set PAT and other configurations."""
162-
from clarifai.utils.cli import validate_context_auth
163-
164-
name = input('context name (default: "default"): ')
165-
user_id = user_id if user_id is not None else input('user id: ')
166-
pat = input_or_default(
167-
'personal access token value (default: "ENVVAR" to get our of env var rather than config): ',
168-
'ENVVAR',
169-
)
170-
171-
# Validate the Context Credentials
172-
validate_context_auth(pat, user_id, api_url)
173-
174-
context = Context(
175-
name,
176-
CLARIFAI_API_BASE=api_url,
177-
CLARIFAI_USER_ID=user_id,
178-
CLARIFAI_PAT=pat,
179-
)
180-
181-
if context.name == '':
182-
context.name = 'default'
183-
184-
ctx.obj.contexts[context.name] = context
185-
ctx.obj.current_context = context.name
186-
187-
ctx.obj.to_yaml()
188-
logger.info(
189-
f"Login successful and Configuration saved successfully for context '{context.name}'"
190-
)
191-
192-
193-
@cli.group(cls=AliasedGroup)
194-
def context():
195-
"""Manage contexts"""
196-
197-
198-
def pat_display(pat):
199-
return pat[:5] + "****"
200-
201-
202-
def input_or_default(prompt, default):
203-
value = input(prompt)
204-
return value if value else default
167+
def current_context(ctx, output_format):
168+
"""Show the current context's details."""
169+
if output_format == 'name':
170+
print(ctx.obj.current_context)
171+
elif output_format == 'json':
172+
print(json.dumps(ctx.obj.contexts[ctx.obj.current_context].to_serializable_dict()))
173+
else:
174+
print(yaml.safe_dump(ctx.obj.contexts[ctx.obj.current_context].to_serializable_dict()))
205175

206176

207-
@context.command()
177+
@config.command(aliases=['create-context', 'set-context'])
208178
@click.argument('name')
209179
@click.option('--user-id', required=False, help='User ID')
210180
@click.option('--base-url', required=False, help='Base URL')
211181
@click.option('--pat', required=False, help='Personal access token')
212182
@click.pass_context
213-
def create(
183+
def create_context(
214184
ctx,
215185
name,
216186
user_id=None,
217187
base_url=None,
218188
pat=None,
219189
):
220-
"""Create a new context"""
190+
"""Create a new context."""
221191
from clarifai.utils.cli import validate_context_auth
222192

223193
if name in ctx.obj.contexts:
@@ -234,22 +204,28 @@ def create(
234204
'personal access token value (default: "ENVVAR" to get our of env var rather than config): ',
235205
'ENVVAR',
236206
)
237-
238-
# Validate the Context Credentials
239207
validate_context_auth(pat, user_id, base_url)
240-
241208
context = Context(name, CLARIFAI_USER_ID=user_id, CLARIFAI_API_BASE=base_url, CLARIFAI_PAT=pat)
242209
ctx.obj.contexts[context.name] = context
243210
ctx.obj.to_yaml()
244211
logger.info(f"Context '{name}' created successfully")
245212

246213

247-
# write a click command to delete a context
248-
@context.command(['rm'])
214+
@config.command(aliases=['e'])
215+
@click.pass_context
216+
def edit(
217+
ctx,
218+
):
219+
"""Open the configuration file for editing."""
220+
# For now, just open the config file (not per-context)
221+
os.system(f'{os.environ.get("EDITOR", "vi")} {ctx.obj.filename}')
222+
223+
224+
@config.command(aliases=['delete-context'])
249225
@click.argument('name')
250226
@click.pass_context
251-
def delete(ctx, name):
252-
"""Delete a context"""
227+
def delete_context(ctx, name):
228+
"""Delete a context."""
253229
if name not in ctx.obj.contexts:
254230
print(f'{name} is not a valid context')
255231
sys.exit(1)
@@ -258,16 +234,29 @@ def delete(ctx, name):
258234
print(f'{name} deleted')
259235

260236

261-
@context.command()
262-
@click.argument('name', type=str)
237+
@config.command(aliases=['get-env'])
263238
@click.pass_context
264-
def use(ctx, name):
265-
"""Set the current context"""
266-
if name not in ctx.obj.contexts:
267-
raise click.UsageError('Context not found')
268-
ctx.obj.current_context = name
269-
ctx.obj.to_yaml()
270-
print(f'Set {name} as the current context')
239+
def env(ctx):
240+
"""Print env vars for the active context."""
241+
ctx.obj.current.print_env_vars()
242+
243+
244+
@config.command(aliases=['show'])
245+
@click.option('-o', '--output-format', default='yaml', type=click.Choice(['json', 'yaml']))
246+
@click.pass_context
247+
def view(ctx, output_format):
248+
"""Display the current configuration."""
249+
config_dict = {
250+
'current-context': ctx.obj.current_context,
251+
'contexts': {
252+
name: context.to_serializable_dict() for name, context in ctx.obj.contexts.items()
253+
},
254+
}
255+
256+
if output_format == 'json':
257+
print(json.dumps(config_dict, indent=2))
258+
else:
259+
print(yaml.safe_dump(config_dict, default_flow_style=False))
271260

272261

273262
@cli.command()

0 commit comments

Comments
 (0)