arize-ax-cli 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ax/commands/cache.py ADDED
@@ -0,0 +1,54 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from ax.config.manager import ConfigManager
6
+ from ax.core.decorators import handle_errors
7
+ from ax.utils.console import confirm, info, success
8
+
9
+ # Create config subcommand app
10
+ app = typer.Typer(
11
+ name="cache",
12
+ help="Manage Arize cache",
13
+ no_args_is_help=True,
14
+ context_settings={"help_option_names": ["--help", "-h"]},
15
+ )
16
+
17
+
18
+ @app.command("clear")
19
+ @handle_errors
20
+ def clear_cache(
21
+ profile: Annotated[
22
+ str,
23
+ typer.Option(
24
+ "--profile",
25
+ "-p",
26
+ help="Profile to show (uses active if not specified)",
27
+ ),
28
+ ] = "",
29
+ verbose: Annotated[
30
+ bool,
31
+ typer.Option(
32
+ "--verbose",
33
+ "-v",
34
+ help="Enable verbose logs",
35
+ ),
36
+ ] = False,
37
+ ) -> None:
38
+ """Clear the Arize SDK cache directory.
39
+
40
+ Removes all cached data to free up space or force refresh.
41
+ """
42
+ if not confirm("Clear the Arize SDK cache?", default=False):
43
+ info("Cache not cleared")
44
+ raise typer.Exit()
45
+
46
+ config = ConfigManager.load(profile)
47
+ cache_dir = config.storage.cache_dir
48
+
49
+ if cache_dir.exists() and cache_dir.is_dir():
50
+ import shutil
51
+
52
+ shutil.rmtree(cache_dir)
53
+ cache_dir.mkdir(parents=True, exist_ok=True)
54
+ success("Cache cleared successfully")
ax/commands/config.py ADDED
@@ -0,0 +1,359 @@
1
+ """Configuration management commands."""
2
+
3
+ import os
4
+ from typing import Annotated
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ from ax.config.manager import ConfigManager
11
+ from ax.config.schema import (
12
+ AuthConfig,
13
+ Config,
14
+ )
15
+ from ax.config.setup import (
16
+ create_config_from_env_vars,
17
+ create_config_interactively,
18
+ detect_env_vars,
19
+ )
20
+ from ax.core.decorators import handle_errors
21
+ from ax.utils.console import (
22
+ confirm,
23
+ emphasis,
24
+ info,
25
+ mask,
26
+ new_line,
27
+ success,
28
+ text,
29
+ text_bold,
30
+ text_dimmed,
31
+ )
32
+
33
+ # Create config subcommand app
34
+ app = typer.Typer(
35
+ name="config",
36
+ help="Manage Arize CLI configuration",
37
+ no_args_is_help=True,
38
+ context_settings={"help_option_names": ["--help", "-h"]},
39
+ )
40
+
41
+ console = Console()
42
+
43
+
44
+ @app.command("init")
45
+ @handle_errors
46
+ def init(
47
+ verbose: Annotated[
48
+ bool,
49
+ typer.Option(
50
+ "--verbose",
51
+ "-v",
52
+ help="Enable verbose logs",
53
+ ),
54
+ ] = False,
55
+ ) -> None:
56
+ """Initialize Arize CLI configuration interactively.
57
+
58
+ Creates a new configuration profile with API key, defaults, and
59
+ preferences. Detects existing ARIZE_* environment variables and offers
60
+ to create config from them.
61
+ """
62
+ existing_profiles = ConfigManager.list_profiles()
63
+
64
+ # Profile Selection
65
+ profile = "default"
66
+ if existing_profiles:
67
+ # profiles exist - prompt for name
68
+ emphasis("Create a new configuration profile")
69
+ text(f"existing profiles: {', '.join(existing_profiles)}\n")
70
+ profile = typer.prompt("profile name")
71
+ new_line()
72
+ else:
73
+ # TODO(Kiko): Need a more captivating welcome message
74
+ emphasis("Welcome to Arize CLI!")
75
+ text("No configuration found. Let's set it up!\n")
76
+
77
+ # Check if profile already exists
78
+ if ConfigManager.exists(profile) and not confirm(
79
+ f"Profile '{profile}' already exists. Overwrite?",
80
+ default=False,
81
+ ):
82
+ info("Configuration unchanged")
83
+ raise typer.Exit()
84
+
85
+ # Environment Variable Detection
86
+ detected_env_vars = detect_env_vars()
87
+ use_env_vars = False
88
+ if detected_env_vars:
89
+ emphasis("Environment Variable Detection\n")
90
+ for field, env_var in detected_env_vars.items():
91
+ value = os.environ.get(env_var, "")
92
+ # Mask API key for display
93
+ if field == "api_key":
94
+ value = mask(value)
95
+ console.print(f" [green]✓[/green] Detected {env_var} = {value}")
96
+
97
+ console.print()
98
+ use_env_vars = confirm(
99
+ "Create config from detected environment variables?",
100
+ default=True,
101
+ )
102
+ console.print()
103
+
104
+ config = (
105
+ create_config_from_env_vars(profile, detected_env_vars)
106
+ if use_env_vars
107
+ else create_config_interactively(profile)
108
+ )
109
+
110
+ # Save configuration
111
+ ConfigManager.save(config, profile)
112
+
113
+ # Set as active profile if not default
114
+ if profile != "default":
115
+ ConfigManager.set_active_profile(profile)
116
+
117
+ # Summary
118
+ new_line()
119
+ success(f"Configuration saved to profile '{profile}'")
120
+ new_line()
121
+ text_dimmed("You're ready to go! Try: ax datasets list")
122
+
123
+
124
+ @app.command("list")
125
+ @handle_errors
126
+ def list_profiles(
127
+ verbose: Annotated[
128
+ bool,
129
+ typer.Option(
130
+ "--verbose",
131
+ "-v",
132
+ help="Enable verbose logs",
133
+ ),
134
+ ] = False,
135
+ ) -> None:
136
+ """List all available configuration profiles.
137
+
138
+ Shows all profiles with the active profile marked.
139
+ """
140
+ profiles = ConfigManager.list_profiles()
141
+
142
+ if not profiles:
143
+ info("No profiles found. Run 'ax config init' to create one.")
144
+ raise typer.Exit()
145
+
146
+ active = ConfigManager.get_active_profile()
147
+
148
+ table = Table(show_header=True, header_style="bold cyan")
149
+ table.add_column("Profile")
150
+ table.add_column("Status")
151
+
152
+ for profile in profiles:
153
+ status = "[green]active[/green]" if profile == active else ""
154
+ table.add_row(profile, status)
155
+
156
+ console.print(table)
157
+ new_line()
158
+
159
+
160
+ @app.command("show")
161
+ @handle_errors
162
+ def show_profile(
163
+ profile: Annotated[
164
+ str,
165
+ typer.Option(
166
+ "--profile",
167
+ "-p",
168
+ help="Profile to show (uses active if not specified)",
169
+ ),
170
+ ] = "",
171
+ all_sections: Annotated[
172
+ bool,
173
+ typer.Option(
174
+ "--all",
175
+ help="Show all sections including defaults",
176
+ ),
177
+ ] = False,
178
+ expand_vars: Annotated[
179
+ bool,
180
+ typer.Option(
181
+ "--expand",
182
+ help="Expand environment variable references",
183
+ ),
184
+ ] = False,
185
+ verbose: Annotated[
186
+ bool,
187
+ typer.Option(
188
+ "--verbose",
189
+ "-v",
190
+ help="Enable verbose logs",
191
+ ),
192
+ ] = False,
193
+ ) -> None:
194
+ """Show configuration for a profile.
195
+
196
+ By default shows env var references like ${ARIZE_API_KEY}.
197
+ Use --expand to show expanded values.
198
+ Use --all to show all sections including defaults.
199
+ """
200
+ # Use profile from context if not specified
201
+ config = ConfigManager.load(profile, expand_vars)
202
+
203
+ # Display configuration
204
+ text_bold(f"\nProfile: {profile}")
205
+ if profile == ConfigManager.get_active_profile():
206
+ console.print("[green](active)[/green]")
207
+ new_line()
208
+
209
+ # Determine which sections to show
210
+ default_config = Config(
211
+ auth=AuthConfig(api_key="dummy"),
212
+ )
213
+
214
+ def is_customized(section_name: str) -> bool:
215
+ """Check if a section has customized (non-default) values."""
216
+ if section_name == "profile" or section_name == "auth":
217
+ return True # Always show these
218
+ config_section = getattr(config, section_name)
219
+ default_section = getattr(default_config, section_name, None)
220
+ if default_section is None:
221
+ return True
222
+ return config_section != default_section
223
+
224
+ # Auth section (always shown)
225
+ emphasis("Authentication:")
226
+ key = config.auth.api_key
227
+ key = key if _is_env_var_ref(key) else mask(key)
228
+ text(f" API Key: {key}")
229
+
230
+ # Routing section
231
+ if all_sections or is_customized("routing"):
232
+ emphasis("\nRouting:")
233
+ if config.routing.region:
234
+ text(f" Region: {config.routing.region}")
235
+ if config.routing.single_host:
236
+ text(f" Single Host: {config.routing.single_host}")
237
+ if config.routing.single_port:
238
+ text(f" Single Port: {config.routing.single_port}")
239
+ if config.routing.base_domain:
240
+ text(f" Base Domain: {config.routing.base_domain}")
241
+ if not (
242
+ config.routing.region
243
+ or config.routing.single_host
244
+ or config.routing.base_domain
245
+ ):
246
+ text(f" API Scheme: {config.routing.api_scheme}")
247
+ text(f" API Host: {config.routing.api_host}")
248
+ text(f" OTLP Scheme: {config.routing.otlp_scheme}")
249
+ text(f" OTLP Host: {config.routing.otlp_host}")
250
+ text(f" Flight Scheme: {config.routing.flight_scheme}")
251
+ text(f" Flight Host: {config.routing.flight_host}")
252
+ text(f" Flight Port: {config.routing.flight_port}")
253
+
254
+ # Transport section
255
+ if all_sections or is_customized("transport"):
256
+ emphasis("\nTransport:")
257
+ text(f" Stream Max Workers: {config.transport.stream_max_workers}")
258
+ text(
259
+ f" Stream Max Queue Bound: {config.transport.stream_max_queue_bound}"
260
+ )
261
+ text(
262
+ f" PyArrow Max Chunksize: {config.transport.pyarrow_max_chunksize}"
263
+ )
264
+ text(
265
+ f" Max HTTP Payload Size: {config.transport.max_http_payload_size_mb} MB"
266
+ )
267
+
268
+ # Security section
269
+ if all_sections or is_customized("security"):
270
+ emphasis("\nSecurity:")
271
+ val = config.security.request_verify
272
+ if _is_bool(str(config.security.request_verify)):
273
+ val = bool(config.security.request_verify)
274
+ text(f" Request Verify: {val}")
275
+
276
+ # Storage section
277
+ if all_sections or is_customized("storage"):
278
+ emphasis("\nStorage:")
279
+ text(f" Directory: {config.storage.directory}")
280
+ text(f" Cache: {config.storage.cache_enabled}")
281
+
282
+ # Output section (always shown)
283
+ emphasis("\nOutput:")
284
+ text(f" Format: {config.output.format}")
285
+
286
+ new_line()
287
+
288
+
289
+ @app.command("use")
290
+ @handle_errors
291
+ def use_profile(
292
+ profile: Annotated[
293
+ str,
294
+ typer.Argument(help="Profile name to activate"),
295
+ ],
296
+ verbose: Annotated[
297
+ bool,
298
+ typer.Option(
299
+ "--verbose",
300
+ "-v",
301
+ help="Enable verbose logs",
302
+ ),
303
+ ] = False,
304
+ ) -> None:
305
+ """Switch to a different configuration profile.
306
+
307
+ Makes the specified profile active for all future commands.
308
+ """
309
+ ConfigManager.set_active_profile(profile)
310
+ success(f"Switched to profile '{profile}'")
311
+
312
+
313
+ @app.command("delete")
314
+ @handle_errors
315
+ def delete_profile(
316
+ profile: Annotated[
317
+ str,
318
+ typer.Argument(help="Profile name to delete"),
319
+ ],
320
+ force: Annotated[
321
+ bool,
322
+ typer.Option(
323
+ "--force",
324
+ "-f",
325
+ help="Skip confirmation",
326
+ ),
327
+ ] = False,
328
+ verbose: Annotated[
329
+ bool,
330
+ typer.Option(
331
+ "--verbose",
332
+ "-v",
333
+ help="Enable verbose logs",
334
+ ),
335
+ ] = False,
336
+ ) -> None:
337
+ """Delete a configuration profile.
338
+
339
+ Cannot delete the default profile or currently active profile.
340
+ """
341
+ if not force and not confirm(
342
+ f"Delete profile '{profile}'?",
343
+ default=False,
344
+ ):
345
+ info("Profile not deleted")
346
+ raise typer.Exit()
347
+
348
+ ConfigManager.delete_profile(profile)
349
+ success(f"Deleted profile '{profile}'")
350
+
351
+
352
+ def _is_bool(val: str) -> bool:
353
+ """Check if a string represents a boolean value."""
354
+ return val.lower() in ("true", "false")
355
+
356
+
357
+ def _is_env_var_ref(val: str) -> bool:
358
+ """Check if a string is an environment variable reference."""
359
+ return val.startswith("${") and val.endswith("}")