Source code for kelvin.sdk.container

"""Dependency wiring (composition root).

This module is responsible for creating and wiring all services.
It's the single place where dependencies are assembled.
"""

from __future__ import annotations

from functools import cached_property
from typing import TYPE_CHECKING

from kelvin.sdk.output import Output
from kelvin.sdk.services import (
    AuthService,
    CredentialStore,
    DockerService,
    SchemaService,
    SessionService,
    TemplateService,
)
from kelvin.sdk.services.agent_sync import AgentService
from kelvin.sdk.services.config import ConfigService

if TYPE_CHECKING:
    from kelvin.api.client import Client
    from kelvin.sdk.commands.app import AppCommands
    from kelvin.sdk.commands.app_images import AppImageCommands
    from kelvin.sdk.commands.apps import AppsCommands
    from kelvin.sdk.commands.auth import AuthCommands
    from kelvin.sdk.commands.mlflow import MLflowCommands
    from kelvin.sdk.commands.secret import SecretCommands
    from kelvin.sdk.commands.workload import WorkloadCommands
    from kelvin.sdk.services.mlflow import MLflowService

# ============ Services Container ============


[docs] class Services: """Container for all services with lazy initialization. Services are created on first access, not at startup. This means: - Faster CLI startup - Commands that don't need auth won't fail if no session exists - Services that depend on other services are created in the right order This is passed via Click context to all commands. Add new services here as your CLI grows. """ def __init__( self, *, json_mode: bool = False, assume_yes: bool = False, verbose: bool = False, ) -> None: self._json_mode: bool = json_mode self._assume_yes: bool = assume_yes self._verbose: bool = verbose @cached_property def output(self) -> Output: """Output service - always safe to access.""" return Output( json_mode=self._json_mode, assume_yes=self._assume_yes, verbose=self._verbose, ) # ============ Flag Setters ============
[docs] def set_verbose(self, value: bool) -> None: """Set verbose flag, propagating to output if already initialized.""" self._verbose = value # cached_property stores its result in the instance __dict__ under the property name. # If "output" is not there yet, the next access will read self._verbose naturally. if "output" in self.__dict__: self.output.verbose = value
[docs] def set_json_mode(self, value: bool) -> None: """Set json_mode flag, propagating to output if already initialized.""" self._json_mode = value if "output" in self.__dict__: self.output.json_mode = value
[docs] def set_assume_yes(self, value: bool) -> None: """Set assume_yes flag, propagating to output if already initialized.""" self._assume_yes = value if "output" in self.__dict__: self.output.assume_yes = value
# ============ Core Infrastructure Services (Leaf Services) ============ @cached_property def credential_store(self) -> CredentialStore: """Credential store - manages secure token storage.""" return CredentialStore() @cached_property def auth(self) -> AuthService: """Auth service - handles OAuth/PKCE authentication flow.""" return AuthService() @cached_property def session(self) -> SessionService: """Session service - manages session state and platform metadata.""" return SessionService() @cached_property def docker(self) -> DockerService: """Docker service - manages Docker operations.""" return DockerService() @cached_property def schema(self) -> SchemaService: """Schema service - manages app configuration schemas.""" return SchemaService() @cached_property def template(self) -> TemplateService: """Template service - manages application templates.""" return TemplateService() @cached_property def agent(self) -> AgentService: """Agent service - syncs agent bundles into applications.""" return AgentService() @cached_property def config_service(self) -> ConfigService: """Config service - manages CLI configuration file.""" return ConfigService() # ============ Business Logic Commands ============ @cached_property def auth_commands(self) -> AuthCommands: """Auth commands - orchestrates auth workflow.""" from kelvin.sdk.commands.auth import AuthCommands return AuthCommands( auth=self.auth, credentials=self.credential_store, session=self.session, docker=self.docker, ) @cached_property def workload_commands(self) -> WorkloadCommands: """Workload commands - manages workload operations.""" from kelvin.sdk.commands.workload import WorkloadCommands return WorkloadCommands(client=self.api_client, schema_service=self.schema) @cached_property def app_commands(self) -> AppCommands: """App commands - manages app creation and operations. Note: api_client and registry_url are NOT passed here to avoid requiring authentication for local-only operations (create, build). The upload command receives them directly from the CLI layer. """ from kelvin.sdk.commands.app import AppCommands return AppCommands( template_service=self.template, docker_service=self.docker, schema_service=self.schema, ) @cached_property def app_image_commands(self) -> AppImageCommands: """App image commands - manages local Docker image operations. These are local-only operations that don't require authentication. """ from kelvin.sdk.commands.app_images import AppImageCommands return AppImageCommands(docker_service=self.docker) @cached_property def apps_commands(self) -> AppsCommands: """Apps commands - manages platform app registry operations. These are API-based operations that require authentication. """ from kelvin.sdk.commands.apps import AppsCommands return AppsCommands(client=self.api_client) @cached_property def secret_commands(self) -> SecretCommands: """Secret commands - manages platform secret operations. These are API-based operations that require authentication. """ from kelvin.sdk.commands.secret import SecretCommands return SecretCommands(client=self.api_client) @cached_property def mlflow_service(self) -> MLflowService: """MLflow service - wraps MLflow registry operations. This is an optional service that requires the mlflow package. """ from kelvin.sdk.services.mlflow import MLflowService return MLflowService() @cached_property def mlflow_commands(self) -> MLflowCommands: """MLflow commands - manages MLflow app creation and model import. These are local operations that don't require authentication, but require the mlflow package to be installed. """ from kelvin.sdk.commands.mlflow import MLflowCommands return MLflowCommands( mlflow_service=self.mlflow_service, template_service=self.template, ) # ============ API Client ============ @cached_property def api_client(self) -> Client: """API client - requires valid session and credentials. Cached after first access. Token is validated and refreshed if needed when the client is first created. Raises ------ NotLoggedInError If no active session exists. CredentialsExpiredError If credentials are expired and cannot be refreshed. """ from kelvin.sdk.services.api_client import APIClientFactory return APIClientFactory( session=self.session, credentials=self.credential_store, auth=self.auth, ).create(verbose=self._verbose)
# ============ Factory Function ============
[docs] def create_services( json_mode: bool = False, assume_yes: bool = False, verbose: bool = False, ) -> Services: """Create services container (lazy initialization). Services are not created here - they're created on first access. This makes startup fast and allows commands that don't need certain services to run without them. Args: json_mode: Enable JSON output mode. assume_yes: Skip confirmation prompts. verbose: Enable verbose/debug output. Returns: Services container with lazy initialization. """ return Services( json_mode=json_mode, assume_yes=assume_yes, verbose=verbose, )