llamactl 0.3.0a13__py3-none-any.whl → 0.3.0a14__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.
@@ -0,0 +1,64 @@
1
+ from dataclasses import replace
2
+
3
+ from llama_deploy.cli.config.schema import Environment
4
+
5
+ from ._config import ConfigManager, config_manager
6
+ from .auth_service import AuthService
7
+
8
+
9
+ class EnvService:
10
+ def __init__(self, config_manager: ConfigManager):
11
+ self.config_manager = config_manager
12
+
13
+ def list_environments(self) -> list[Environment]:
14
+ return self.config_manager.list_environments()
15
+
16
+ def get_current_environment(self) -> Environment:
17
+ return self.config_manager.get_current_environment()
18
+
19
+ def switch_environment(self, api_url: str) -> Environment:
20
+ env = self.config_manager.get_environment(api_url)
21
+ if not env:
22
+ raise ValueError(
23
+ f"Environment '{api_url}' not found. Add it with 'llamactl auth env add <API_URL>'"
24
+ )
25
+ self.config_manager.set_settings_current_environment(api_url)
26
+ self.config_manager.set_settings_current_profile(None)
27
+ return env
28
+
29
+ def create_or_update_environment(self, env: Environment) -> None:
30
+ self.config_manager.create_or_update_environment(
31
+ env.api_url, env.requires_auth, env.min_llamactl_version
32
+ )
33
+
34
+ def delete_environment(self, api_url: str) -> bool:
35
+ return self.config_manager.delete_environment(api_url)
36
+
37
+ def current_auth_service(self) -> AuthService:
38
+ return AuthService(self.config_manager, self.get_current_environment())
39
+
40
+ def auto_update_env(self, env: Environment) -> Environment:
41
+ svc = AuthService(self.config_manager, env)
42
+ version = svc.fetch_server_version()
43
+ update = replace(env)
44
+ update.requires_auth = version.requires_auth
45
+ update.min_llamactl_version = version.min_llamactl_version
46
+ if update != env:
47
+ self.config_manager.create_or_update_environment(
48
+ update.api_url, update.requires_auth, update.min_llamactl_version
49
+ )
50
+ return update
51
+
52
+ def probe_environment(self, api_url: str) -> Environment:
53
+ clean = api_url.rstrip("/")
54
+ base_env = Environment(
55
+ api_url=clean, requires_auth=False, min_llamactl_version=None
56
+ )
57
+ svc = AuthService(self.config_manager, base_env)
58
+ version = svc.fetch_server_version()
59
+ base_env.requires_auth = version.requires_auth
60
+ base_env.min_llamactl_version = version.min_llamactl_version
61
+ return base_env
62
+
63
+
64
+ service = EnvService(config_manager)
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Auth:
6
+ """Auth Profile configuration"""
7
+
8
+ name: str
9
+ api_url: str
10
+ project_id: str
11
+ api_key: str | None = None
12
+
13
+
14
+ @dataclass
15
+ class Environment:
16
+ """Environment configuration stored in SQLite.
17
+
18
+ Note: `api_url`, `requires_auth`, and `min_llamactl_version` are persisted
19
+ in the environments table.
20
+ """
21
+
22
+ api_url: str
23
+ requires_auth: bool
24
+ min_llamactl_version: str | None = None
25
+
26
+
27
+ DEFAULT_ENVIRONMENT = Environment(
28
+ api_url="https://api.cloud.llamaindex.ai",
29
+ requires_auth=True,
30
+ min_llamactl_version=None,
31
+ )
@@ -1,52 +1,13 @@
1
1
  """Shared utilities for CLI operations"""
2
2
 
3
3
  import questionary
4
- from rich import print as rprint
5
4
  from rich.console import Console
6
5
 
7
- from ..config import config_manager
8
6
  from .session_utils import is_interactive_session
9
7
 
10
8
  console = Console()
11
9
 
12
10
 
13
- def select_profile(profile_name: str | None = None) -> str | None:
14
- """
15
- Select a profile interactively if name not provided.
16
- Returns the selected profile name or None if cancelled.
17
-
18
- In non-interactive sessions, returns None if profile_name is not provided.
19
- """
20
- if profile_name:
21
- return profile_name
22
-
23
- # Don't attempt interactive selection in non-interactive sessions
24
- if not is_interactive_session():
25
- return None
26
-
27
- try:
28
- profiles = config_manager.list_profiles()
29
-
30
- if not profiles:
31
- rprint("[yellow]No profiles found[/yellow]")
32
- return None
33
-
34
- choices = []
35
- current_name = config_manager.get_current_profile_name()
36
-
37
- for profile in profiles:
38
- title = f"{profile.name} ({profile.api_url})"
39
- if profile.name == current_name:
40
- title += " [current]"
41
- choices.append(questionary.Choice(title=title, value=profile.name))
42
-
43
- return questionary.select("Select profile:", choices=choices).ask()
44
-
45
- except Exception as e:
46
- rprint(f"[red]Error loading profiles: {e}[/red]")
47
- return None
48
-
49
-
50
11
  def confirm_action(message: str, default: bool = False) -> bool:
51
12
  """
52
13
  Ask for confirmation with a consistent interface.
@@ -2,7 +2,6 @@ import logging
2
2
  from typing import Callable, ParamSpec, TypeVar
3
3
 
4
4
  import click
5
- from llama_deploy.cli.config import config_manager
6
5
  from llama_deploy.cli.interactive_prompts.session_utils import is_interactive_session
7
6
 
8
7
  from .debug import setup_file_logging
@@ -32,14 +31,6 @@ def global_options(f: Callable[P, R]) -> Callable[P, R]:
32
31
  )(f)
33
32
 
34
33
 
35
- def control_plane_url_callback(
36
- ctx: click.Context, param: click.Parameter, value: str
37
- ) -> str:
38
- if value:
39
- config_manager.set_default_control_plane_url(value)
40
- return value
41
-
42
-
43
34
  def interactive_option(f: Callable[P, R]) -> Callable[P, R]:
44
35
  """Add an interactive option to the command"""
45
36
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llamactl
3
- Version: 0.3.0a13
3
+ Version: 0.3.0a14
4
4
  Summary: A command-line interface for managing LlamaDeploy projects and deployments
5
5
  Author: Adrian Lyjak
6
6
  Author-email: Adrian Lyjak <adrianlyjak@gmail.com>
7
7
  License: MIT
8
- Requires-Dist: llama-deploy-core[client]>=0.3.0a13,<0.4.0
9
- Requires-Dist: llama-deploy-appserver>=0.3.0a13,<0.4.0
8
+ Requires-Dist: llama-deploy-core[client]>=0.3.0a14,<0.4.0
9
+ Requires-Dist: llama-deploy-appserver>=0.3.0a14,<0.4.0
10
10
  Requires-Dist: httpx>=0.24.0
11
11
  Requires-Dist: rich>=13.0.0
12
12
  Requires-Dist: questionary>=2.0.0
@@ -1,20 +1,23 @@
1
- llama_deploy/cli/__init__.py,sha256=06afbb37cf01a3880f0487c3ab83a44ce72153b6ef8c2d6c0ed4324f03a5ae8f,411
2
- llama_deploy/cli/app.py,sha256=a909fcb06f6c2179986fefa5b561e597840aa14a0ee8c038b238d267c3e2a178,2345
3
- llama_deploy/cli/client.py,sha256=383601a4b9278972b4d8350a277d517d9b22a22776c1c4d6cd3f07db53f8d871,2098
1
+ llama_deploy/cli/__init__.py,sha256=df028686233c4d5a3e244bb50c1c7b84cf2399ae03abe45eb4d01e53caa1be38,476
2
+ llama_deploy/cli/app.py,sha256=9170e4f506c482522bd745eb1cdb700a198cfcfd7204c168c94e5ee2b6b43ffa,2199
3
+ llama_deploy/cli/client.py,sha256=f0f72c90cddfbc9198e154883f3b8f05fb47dbe7ec1f5755106dbb8009d2bb54,1459
4
4
  llama_deploy/cli/commands/aliased_group.py,sha256=bc41007c97b7b93981217dbd4d4591df2b6c9412a2d9ed045b0ec5655ed285f2,1066
5
- llama_deploy/cli/commands/auth.py,sha256=145dfb175223ee0262677eed69806eb5e1b160574e9b480da6311ae80241141f,12806
6
- llama_deploy/cli/commands/deployment.py,sha256=7464e09ad53f082667a1e7e3f6c0040d01fcb312cbc92cf182efc66260a55e78,10454
5
+ llama_deploy/cli/commands/auth.py,sha256=de584d11c1acf5a4e7aee8c8f30184335053ed206d38dbc8509b2d1d0677a092,12640
6
+ llama_deploy/cli/commands/deployment.py,sha256=c99feb73a887063cad86e2cc555f21ebac6d47576749d79c551f26b8567af638,9879
7
+ llama_deploy/cli/commands/env.py,sha256=e0b96b9f4e7921b4370ad5f8bc0a2bfb19b705e73004f72c37c9bed28a208e0d,6702
7
8
  llama_deploy/cli/commands/init.py,sha256=51b2de1e35ff34bc15c9dfec72fbad08aaf528c334df168896d36458a4e9401c,6307
8
9
  llama_deploy/cli/commands/serve.py,sha256=4d47850397ba172944df56a934a51bedb52403cbd3f9b000b1ced90a31c75049,2721
9
- llama_deploy/cli/config.py,sha256=0864c6e25646c28062b1adc081039bd59cbd9d87ab7e4a6e465660c6231f0489,8951
10
+ llama_deploy/cli/config/_config.py,sha256=31376c3f3ecadaf52545e4aeb4a660dde2c22d7e6c33b232ec93eebeb926e3fb,14953
11
+ llama_deploy/cli/config/auth_service.py,sha256=38a2de9bbcf5780675130d90e4f9116da190fd31a33e1c178a7f3847b943c229,2667
12
+ llama_deploy/cli/config/env_service.py,sha256=6e50ffe2e33dcff4193c87b3df8daac71542d406314a7bcaf48187715a53780e,2445
13
+ llama_deploy/cli/config/schema.py,sha256=086b6161b238c2037068a2b510f5d4bbda917494df764818ff9692e9735a8953,608
10
14
  llama_deploy/cli/debug.py,sha256=e85a72d473bbe1645eb31772f7349bde703d45704166f767385895c440afc762,496
11
15
  llama_deploy/cli/env.py,sha256=6ebc24579815b3787829c81fd5bb9f31698a06e62c0128a788559f962b33a7af,1016
12
16
  llama_deploy/cli/interactive_prompts/session_utils.py,sha256=b996f2eddf70d6c49636c4797d246d212fce0950fe7e9a3f59cf6a1bf7ae26f5,1142
13
- llama_deploy/cli/interactive_prompts/utils.py,sha256=2b29148f2cf2be8d1642fd800f529602cecb03997259790a3b8e1b879604cee2,1736
14
- llama_deploy/cli/options.py,sha256=9eb6f1c5d89f83cbf5b288d3a85c38acd929168d6afdf4b04d664df419aa1816,1543
17
+ llama_deploy/cli/interactive_prompts/utils.py,sha256=594cc2a242cc3405d66d0e26a60647496cc5fcb4ce7d0500a4cfec4888c9a0fa,516
18
+ llama_deploy/cli/options.py,sha256=62ee7286c3305ddb4b597783d19e854284d79bf9384800045f15b934dc245c1d,1298
15
19
  llama_deploy/cli/platform_client.py,sha256=69de23dc79a8f5922afc9e3bac1b633a531340ebbefeb7838e3a88419faa754c,1451
16
20
  llama_deploy/cli/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
17
- llama_deploy/cli/textual/api_key_profile_form.py,sha256=cf059426d758136318c3b6f340e2741e79ab1ec613b8741ef3d2a402214ea2c4,22253
18
21
  llama_deploy/cli/textual/deployment_form.py,sha256=1cf186b765d10a1bdd7394f22ddd7598d75dba8c9a8a7a8be74e6151da2f31dc,20941
19
22
  llama_deploy/cli/textual/deployment_help.py,sha256=d43e9ff29db71a842cf8b491545763d581ede3132b8af518c73af85a40950046,2464
20
23
  llama_deploy/cli/textual/deployment_monitor.py,sha256=7bcf3f0213401c2432fdb5a9d9acf468a4afe83b2d86d7f2852319768e6f2534,17231
@@ -23,7 +26,7 @@ llama_deploy/cli/textual/github_callback_server.py,sha256=dc74c510f8a98ef6ffaab0
23
26
  llama_deploy/cli/textual/llama_loader.py,sha256=33cb32a46dd40bcf889c553e44f2672c410e26bd1d4b17aa6cca6d0a5d59c2c4,1468
24
27
  llama_deploy/cli/textual/secrets_form.py,sha256=a43fbd81aad034d0d60906bfd917c107f9ace414648b0f63ac0b29eeba4050db,7061
25
28
  llama_deploy/cli/textual/styles.tcss,sha256=b1a54dc5fb0e0aa12cbf48807e9e6a94b9926838b8058dae1336a134f02e92b0,3327
26
- llamactl-0.3.0a13.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
27
- llamactl-0.3.0a13.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
28
- llamactl-0.3.0a13.dist-info/METADATA,sha256=87e30b0475ce6d5eaf7d400e4c5c00d41d2cabb0ae54abc02ab686659c1b1e15,3177
29
- llamactl-0.3.0a13.dist-info/RECORD,,
29
+ llamactl-0.3.0a14.dist-info/WHEEL,sha256=66530aef82d5020ef5af27ae0123c71abb9261377c5bc519376c671346b12918,79
30
+ llamactl-0.3.0a14.dist-info/entry_points.txt,sha256=b67e1eb64305058751a651a80f2d2268b5f7046732268421e796f64d4697f83c,52
31
+ llamactl-0.3.0a14.dist-info/METADATA,sha256=9b2916ded8d6a8edfa8ee764b30b115eba00d9bbcb95b5c98e37def2e9bee3f1,3177
32
+ llamactl-0.3.0a14.dist-info/RECORD,,
@@ -1,241 +0,0 @@
1
- """Configuration and profile management for llamactl"""
2
-
3
- import os
4
- import sqlite3
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
- from typing import List
8
-
9
-
10
- @dataclass
11
- class Profile:
12
- """Profile configuration"""
13
-
14
- name: str
15
- api_url: str
16
- project_id: str
17
- api_key_auth_token: str | None = None
18
-
19
-
20
- class ConfigManager:
21
- """Manages profiles and configuration using SQLite"""
22
-
23
- def __init__(self):
24
- self.config_dir = self._get_config_dir()
25
- self.db_path = self.config_dir / "profiles.db"
26
- self._ensure_config_dir()
27
- self._init_database()
28
- self.default_control_plane_url = "https://api.llamacloud.com"
29
-
30
- def _get_config_dir(self) -> Path:
31
- """Get the configuration directory path based on OS.
32
-
33
- Honors LLAMACTL_CONFIG_DIR when set. This helps tests isolate state.
34
- """
35
- override = os.environ.get("LLAMACTL_CONFIG_DIR")
36
- if override:
37
- return Path(override).expanduser()
38
- if os.name == "nt": # Windows
39
- config_dir = Path(os.environ.get("APPDATA", "~")) / "llamactl"
40
- else: # Unix-like (Linux, macOS)
41
- config_dir = Path.home() / ".config" / "llamactl"
42
- return config_dir.expanduser()
43
-
44
- def _ensure_config_dir(self):
45
- """Create configuration directory if it doesn't exist"""
46
- self.config_dir.mkdir(parents=True, exist_ok=True)
47
-
48
- def _init_database(self):
49
- """Initialize SQLite database with required tables"""
50
- with sqlite3.connect(self.db_path) as conn:
51
- # Check if we need to migrate from old schema
52
- cursor = conn.execute("PRAGMA table_info(profiles)")
53
- columns = [row[1] for row in cursor.fetchall()]
54
-
55
- # Migration: handle old active_project_id -> project_id and make it required
56
- if "active_project_id" in columns and "project_id" not in columns:
57
- # Delete any profiles that have no active_project_id since project_id is now required
58
- conn.execute(
59
- "DELETE FROM profiles WHERE active_project_id IS NULL OR active_project_id = ''"
60
- )
61
-
62
- # Rename active_project_id to project_id
63
- # Note: SQLite doesn't allow changing column constraints easily, but we enforce
64
- # the NOT NULL constraint in our application code and new table creation
65
- conn.execute(
66
- "ALTER TABLE profiles RENAME COLUMN active_project_id TO project_id"
67
- )
68
-
69
- # Add api_key_auth_token column if not already present
70
- mig_cursor = conn.execute("PRAGMA table_info(profiles)")
71
- mig_columns = [row[1] for row in mig_cursor.fetchall()]
72
- if "api_key_auth_token" not in mig_columns:
73
- conn.execute(
74
- "ALTER TABLE profiles ADD COLUMN api_key_auth_token TEXT"
75
- )
76
-
77
- # Create tables with new schema (this will only create if they don't exist)
78
- conn.execute("""
79
- CREATE TABLE IF NOT EXISTS profiles (
80
- name TEXT PRIMARY KEY,
81
- api_url TEXT NOT NULL,
82
- project_id TEXT NOT NULL,
83
- api_key_auth_token TEXT
84
- )
85
- """)
86
-
87
- conn.execute("""
88
- CREATE TABLE IF NOT EXISTS settings (
89
- key TEXT PRIMARY KEY,
90
- value TEXT NOT NULL
91
- )
92
- """)
93
-
94
- conn.commit()
95
-
96
- def create_profile(
97
- self,
98
- name: str,
99
- api_url: str,
100
- project_id: str,
101
- api_key_auth_token: str | None = None,
102
- ) -> Profile:
103
- """Create a new profile"""
104
- if not project_id.strip():
105
- raise ValueError("Project ID is required")
106
- profile = Profile(
107
- name=name,
108
- api_url=api_url,
109
- project_id=project_id,
110
- api_key_auth_token=api_key_auth_token,
111
- )
112
-
113
- with sqlite3.connect(self.db_path) as conn:
114
- try:
115
- conn.execute(
116
- "INSERT INTO profiles (name, api_url, project_id, api_key_auth_token) VALUES (?, ?, ?, ?)",
117
- (
118
- profile.name,
119
- profile.api_url,
120
- profile.project_id,
121
- profile.api_key_auth_token,
122
- ),
123
- )
124
- conn.commit()
125
- except sqlite3.IntegrityError:
126
- raise ValueError(f"Profile '{name}' already exists")
127
-
128
- return profile
129
-
130
- def get_profile(self, name: str) -> Profile | None:
131
- """Get a profile by name"""
132
- with sqlite3.connect(self.db_path) as conn:
133
- cursor = conn.execute(
134
- "SELECT name, api_url, project_id, api_key_auth_token FROM profiles WHERE name = ?",
135
- (name,),
136
- )
137
- row = cursor.fetchone()
138
- if row:
139
- return Profile(
140
- name=row[0],
141
- api_url=row[1],
142
- project_id=row[2],
143
- api_key_auth_token=row[3],
144
- )
145
- return None
146
-
147
- def list_profiles(self) -> List[Profile]:
148
- """List all profiles"""
149
- profiles = []
150
- with sqlite3.connect(self.db_path) as conn:
151
- cursor = conn.execute(
152
- "SELECT name, api_url, project_id, api_key_auth_token FROM profiles ORDER BY name"
153
- )
154
- for row in cursor.fetchall():
155
- profiles.append(
156
- Profile(
157
- name=row[0],
158
- api_url=row[1],
159
- project_id=row[2],
160
- api_key_auth_token=row[3],
161
- )
162
- )
163
- return profiles
164
-
165
- def delete_profile(self, name: str) -> bool:
166
- """Delete a profile by name. Returns True if deleted, False if not found."""
167
- with sqlite3.connect(self.db_path) as conn:
168
- cursor = conn.execute("DELETE FROM profiles WHERE name = ?", (name,))
169
- conn.commit()
170
-
171
- # If this was the active profile, clear it
172
- if self.get_current_profile_name() == name:
173
- self.set_current_profile(None)
174
-
175
- return cursor.rowcount > 0
176
-
177
- def set_current_profile(self, name: str | None):
178
- """Set the current active profile"""
179
- with sqlite3.connect(self.db_path) as conn:
180
- if name is None:
181
- conn.execute("DELETE FROM settings WHERE key = 'current_profile'")
182
- else:
183
- conn.execute(
184
- "INSERT OR REPLACE INTO settings (key, value) VALUES ('current_profile', ?)",
185
- (name,),
186
- )
187
- conn.commit()
188
-
189
- def get_current_profile_name(self) -> str | None:
190
- """Get the name of the current active profile"""
191
- with sqlite3.connect(self.db_path) as conn:
192
- cursor = conn.execute(
193
- "SELECT value FROM settings WHERE key = 'current_profile'"
194
- )
195
- row = cursor.fetchone()
196
- return row[0] if row else None
197
-
198
- def get_current_profile(self) -> Profile | None:
199
- """Get the current active profile"""
200
- current_name = self.get_current_profile_name()
201
- if current_name:
202
- return self.get_profile(current_name)
203
- profiles = self.list_profiles()
204
- if len(profiles) == 1:
205
- return profiles[0]
206
- return None
207
-
208
- def set_project(self, profile_name: str, project_id: str) -> bool:
209
- """Set the project for a profile. Returns True if profile exists."""
210
- with sqlite3.connect(self.db_path) as conn:
211
- cursor = conn.execute(
212
- "UPDATE profiles SET project_id = ? WHERE name = ?",
213
- (project_id, profile_name),
214
- )
215
- conn.commit()
216
- return cursor.rowcount > 0
217
-
218
- def set_default_control_plane_url(self, url: str) -> None:
219
- """Set the default control plane URL for the current session"""
220
- self.default_control_plane_url = url
221
-
222
- def get_project(self, profile_name: str) -> str | None:
223
- """Get the project for a profile"""
224
- profile = self.get_profile(profile_name)
225
- return profile.project_id if profile else None
226
-
227
- def set_api_key_auth_token(
228
- self, profile_name: str, api_key_auth_token: str | None
229
- ) -> bool:
230
- """Set the API key auth token for a profile. Returns True if profile exists."""
231
- with sqlite3.connect(self.db_path) as conn:
232
- cursor = conn.execute(
233
- "UPDATE profiles SET api_key_auth_token = ? WHERE name = ?",
234
- (api_key_auth_token, profile_name),
235
- )
236
- conn.commit()
237
- return cursor.rowcount > 0
238
-
239
-
240
- # Global config manager instance
241
- config_manager = ConfigManager()