Source code for air_sdk.endpoints.user_configs

# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: MIT
from __future__ import annotations

from dataclasses import dataclass, field
from io import TextIOBase
from pathlib import Path
from typing import TYPE_CHECKING, Any, Union

from air_sdk.air_model import AirModel, BaseEndpointAPI, PrimaryKey
from air_sdk.bc import (
    BaseCompatMixin,
    UserConfigEndpointAPICompatMixin,
)
from air_sdk.endpoints import mixins
from air_sdk.utils import (
    validate_payload_types,
)

if TYPE_CHECKING:
    pass


[docs] @dataclass(eq=False) class UserConfig(BaseCompatMixin, AirModel): id: str name: str kind: str content: str | None = field(default=None, metadata=AirModel.FIELD_LAZY, repr=False) # TODO: Add these fields once ResourceBudget endpoint is implemented in v3: # organization: str | None = field(repr=False) # organization_budget: ResourceBudget | None = field( # metadata=AirModel.FIELD_FOREIGN_KEY, repr=False # ) KIND_CLOUD_INIT_USER_DATA = 'cloud-init-user-data' KIND_CLOUD_INIT_META_DATA = 'cloud-init-meta-data'
[docs] @classmethod def get_model_api(cls) -> type[UserConfigEndpointAPI]: return UserConfigEndpointAPI
@property def model_api(self) -> UserConfigEndpointAPI: return self.get_model_api()(self.__api__)
[docs] @validate_payload_types def update(self, **kwargs: Any) -> None: self._ensure_pk_exists('updated') self.model_api.update(user_config=self, **kwargs) self.refresh()
[docs] class UserConfigEndpointAPI( UserConfigEndpointAPICompatMixin, mixins.ListApiMixin[UserConfig], mixins.CreateApiMixin[UserConfig], mixins.GetApiMixin[UserConfig], mixins.PatchApiMixin[UserConfig], mixins.DeleteApiMixin, BaseEndpointAPI[UserConfig], ): API_PATH = 'userconfigs' model = UserConfig @staticmethod def _resolve_user_config_content(content: Union[str, Path, TextIOBase]) -> str: """Resolve user config content from various input types. Args: content: Content as one of: - str: Literal content string (never interpreted as file path) - Path: File path to read from (must exist) - TextIOBase: File handle to read from Returns: Resolved content as string Raises: FileNotFoundError: If Path doesn't exist ValueError: If content type is not supported Examples: >>> # Literal string content >>> api.user_configs.create(content="#!/bin/bash\\necho 'hello'") >>> # Read from file (explicit Path object) >>> from pathlib import Path >>> api.user_configs.create(content=Path('/path/to/script.sh')) >>> # Read from file handle >>> with open('/path/to/script.sh') as f: ... api.user_configs.create(content=f) """ if isinstance(content, str): # String is always treated as literal content (never as file path) # This removes ambiguity - users must use Path() for file inputs return content elif isinstance(content, Path): with content.open('r') as content_file: return content_file.read() elif isinstance(content, TextIOBase): # File handle return content.read() else: raise ValueError( f'Unsupported content type: {type(content)}. ' f'Expected str (literal content), Path (file path), or file handle.' )
[docs] @validate_payload_types def create(self, **kwargs: Any) -> UserConfig: # Check for required parameters if 'name' not in kwargs: raise TypeError("create() missing required keyword argument: 'name'") if 'kind' not in kwargs: raise TypeError("create() missing required keyword argument: 'kind'") if 'content' not in kwargs: raise TypeError("create() missing required keyword argument: 'content'") # Resolve content from various input types content = kwargs.pop('content') resolved_content = self._resolve_user_config_content(content) payload = { 'name': kwargs.pop('name'), 'kind': kwargs.pop('kind'), 'content': resolved_content, **kwargs, } return super().create(**payload)
[docs] @validate_payload_types def patch(self, pk: PrimaryKey, **kwargs: Any) -> UserConfig: # Resolve content from various input types (string, Path, file handle) if 'content' in kwargs: kwargs['content'] = self._resolve_user_config_content(kwargs['content']) return super().patch(pk, **kwargs) # type: ignore[no-any-return,misc]
[docs] @validate_payload_types def update( self, *, user_config: UserConfig | PrimaryKey, **kwargs: Any, ) -> UserConfig: user_config_id = ( user_config.id if isinstance(user_config, UserConfig) else user_config ) return self.patch(user_config_id, **kwargs)