Coverage for src/chuck_data/commands/wizard/validator.py: 0%
85 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
1"""
2Input validation for setup wizard steps.
3"""
5from dataclasses import dataclass
6from typing import Optional, List, Dict, Any
7import logging
9from ...databricks.url_utils import (
10 validate_workspace_url,
11 normalize_workspace_url,
12 detect_cloud_provider,
13 get_full_workspace_url,
14)
17@dataclass
18class ValidationResult:
19 """Result of input validation."""
21 is_valid: bool
22 message: str
23 processed_value: Optional[str] = None
24 error_details: Optional[str] = None
27class InputValidator:
28 """Handles validation of user inputs for wizard steps."""
30 def validate_workspace_url(self, url_input: str) -> ValidationResult:
31 """Validate and process workspace URL input."""
32 if not url_input or not url_input.strip():
33 return ValidationResult(
34 is_valid=False, message="Workspace URL cannot be empty"
35 )
37 url_input = url_input.strip()
39 try:
40 # First validate the raw input before processing
41 is_raw_valid, raw_error = validate_workspace_url(url_input)
43 if not is_raw_valid:
44 return ValidationResult(
45 is_valid=False,
46 message=raw_error or "Invalid workspace URL format",
47 )
49 # If raw input is valid, process it
50 normalized_id = normalize_workspace_url(url_input)
51 cloud_provider = detect_cloud_provider(url_input)
52 full_url = get_full_workspace_url(normalized_id, cloud_provider)
54 return ValidationResult(
55 is_valid=True,
56 message="Workspace URL validated successfully",
57 processed_value=full_url,
58 )
60 except Exception as e:
61 logging.error(f"Error processing workspace URL: {e}")
62 return ValidationResult(
63 is_valid=False,
64 message="Error processing workspace URL",
65 error_details=str(e),
66 )
68 def validate_token(self, token: str, workspace_url: str) -> ValidationResult:
69 """Validate Databricks token."""
70 if not token or not token.strip():
71 return ValidationResult(is_valid=False, message="Token cannot be empty")
73 token = token.strip()
75 try:
76 # Validate token with Databricks API using the provided workspace URL
77 from ...clients.databricks import DatabricksAPIClient
79 client = DatabricksAPIClient(workspace_url, token)
80 is_valid = client.validate_token()
82 if not is_valid:
83 return ValidationResult(
84 is_valid=False,
85 message="Invalid Databricks token - please check and try again",
86 )
88 return ValidationResult(
89 is_valid=True,
90 message="Token validated successfully",
91 processed_value=token,
92 )
94 except Exception as e:
95 logging.error(f"Error validating token: {e}")
96 return ValidationResult(
97 is_valid=False, message="Error validating token", error_details=str(e)
98 )
100 def validate_model_selection(
101 self, model_input: str, models: List[Dict[str, Any]]
102 ) -> ValidationResult:
103 """Validate model selection input."""
104 if not model_input or not model_input.strip():
105 return ValidationResult(
106 is_valid=False,
107 message="Please select a model by entering its number or name",
108 )
110 if not models:
111 return ValidationResult(
112 is_valid=False, message="No models available for selection"
113 )
115 model_input = model_input.strip()
117 # Try to interpret as an index first
118 if model_input.isdigit():
119 index = int(model_input) - 1 # Convert to 0-based index
120 if 0 <= index < len(models):
121 selected_model = models[index]["name"]
122 return ValidationResult(
123 is_valid=True,
124 message=f"Model '{selected_model}' selected",
125 processed_value=selected_model,
126 )
127 else:
128 return ValidationResult(
129 is_valid=False,
130 message=f"Invalid model number. Please enter a number between 1 and {len(models)}",
131 )
133 # Try to find by exact name (case-insensitive)
134 for model in models:
135 if model_input.lower() == model["name"].lower():
136 return ValidationResult(
137 is_valid=True,
138 message=f"Model '{model['name']}' selected",
139 processed_value=model["name"],
140 )
142 # Try substring match
143 matches = []
144 for model in models:
145 if model_input.lower() in model["name"].lower():
146 matches.append(model["name"])
148 if len(matches) == 1:
149 return ValidationResult(
150 is_valid=True,
151 message=f"Model '{matches[0]}' selected",
152 processed_value=matches[0],
153 )
154 elif len(matches) > 1:
155 return ValidationResult(
156 is_valid=False,
157 message=f"Multiple models match '{model_input}'. Please be more specific or use a number",
158 error_details=f"Matching models: {', '.join(matches)}",
159 )
161 return ValidationResult(
162 is_valid=False,
163 message=f"Model '{model_input}' not found. Please enter a valid model number or name",
164 )
166 def validate_usage_consent(self, response: str) -> ValidationResult:
167 """Validate usage consent response."""
168 if not response or not response.strip():
169 return ValidationResult(
170 is_valid=False, message="Please enter 'yes' or 'no'"
171 )
173 response = response.strip().lower()
175 if response in ["yes", "y"]:
176 return ValidationResult(
177 is_valid=True,
178 message="Usage tracking consent granted",
179 processed_value="yes",
180 )
181 elif response in ["no", "n"]:
182 return ValidationResult(
183 is_valid=True,
184 message="Usage tracking consent declined",
185 processed_value="no",
186 )
187 else:
188 return ValidationResult(
189 is_valid=False, message="Please enter 'yes' or 'no'"
190 )
192 def detect_input_type(self, input_text: str, current_step) -> str:
193 """Detect what type of input this is based on content and current step."""
194 if not input_text or not input_text.strip():
195 return "empty"
197 input_text = input_text.strip()
199 # For workspace URL step, detect URL-like input
200 if (
201 hasattr(current_step, "WORKSPACE_URL")
202 and current_step == current_step.WORKSPACE_URL
203 ):
204 has_dots = "." in input_text
205 has_databricks = "databricks" in input_text.lower()
206 has_protocol = input_text.lower().startswith("http")
208 if has_dots or has_databricks or has_protocol:
209 return "url"
210 else:
211 return "token" # Assume non-URL input is token
213 return "text"