plano/cli/planoai/config_providers.py
Adil Hafeez 5774452195 Refactor config_generator into modular, testable components
Break the 507-line monolithic validate_and_render_schema() into focused
modules with pure validation functions, proper error handling, and clean
I/O separation:

- config_providers.py: provider constants, ConfigValidationError, unified
  URL parsing (replaces 3 different inline implementations)
- config_validator.py: 11 pure validation functions (no I/O, no print/exit)
- config_generator.py: thin 146-line I/O orchestrator, reads files once
  (was twice), uses logging instead of print()

Also cleans up module responsibilities:
- Move stream_access_logs from utils.py to docker_cli.py (Docker operation)
- Deduplicate llm_providers->model_providers migration
- Fix "Model alias 2 -" debug artifact in error message
- Update docker-compose.dev.yaml volume mounts for new files
- Rewrite tests: 53 tests calling pure functions directly (no mock_open
  chains), up from 10 brittle mock-dependent tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 05:16:34 +00:00

87 lines
2 KiB
Python

"""Model provider constants, custom exception, and URL parsing utility."""
import logging
from urllib.parse import urlparse
log = logging.getLogger(__name__)
class ConfigValidationError(Exception):
"""Raised when config validation fails."""
pass
# --- Provider Constants ---
SUPPORTED_PROVIDERS_WITH_BASE_URL = [
"azure_openai",
"ollama",
"qwen",
"amazon_bedrock",
"arch",
]
SUPPORTED_PROVIDERS_WITHOUT_BASE_URL = [
"deepseek",
"groq",
"mistral",
"openai",
"gemini",
"anthropic",
"together_ai",
"xai",
"moonshotai",
"zhipu",
]
SUPPORTED_PROVIDERS = (
SUPPORTED_PROVIDERS_WITHOUT_BASE_URL + SUPPORTED_PROVIDERS_WITH_BASE_URL
)
INTERNAL_PROVIDERS = {
"arch-function": {
"name": "arch-function",
"provider_interface": "arch",
"model": "Arch-Function",
"internal": True,
},
"plano-orchestrator": {
"name": "plano-orchestrator",
"provider_interface": "arch",
"model": "Plano-Orchestrator",
"internal": True,
},
}
def parse_url_endpoint(url):
"""Parse a URL into endpoint, port, protocol, and optional path_prefix.
Replaces the old get_endpoint_and_port() and inline urlparse logic.
Raises ConfigValidationError for invalid URLs.
Returns dict with keys: endpoint, port, protocol, path_prefix (optional)
"""
result = urlparse(url)
if not result.scheme or result.scheme not in ("http", "https"):
raise ConfigValidationError(
f"Invalid URL '{url}': scheme must be http or https"
)
if not result.hostname:
raise ConfigValidationError(f"Invalid URL '{url}': hostname is required")
port = result.port
if port is None:
port = 80 if result.scheme == "http" else 443
parsed = {
"endpoint": result.hostname,
"port": port,
"protocol": result.scheme,
}
if result.path and result.path != "/":
parsed["path_prefix"] = result.path
return parsed