Source code for air_sdk

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

from __future__ import annotations

from datetime import timedelta
from typing import TYPE_CHECKING

from air_sdk import const
from air_sdk.bc.decorators import deprecated
from air_sdk.client import Client

__all__ = [
    'const',
    'AirApi',
    'Client',
    'AirModelAttributeError',
    # Type definitions for API payloads
    'DockerRunParameters',
    'DockerRunTmpfsParameter',
    'EmulationParams',
    'Platform',
    'ResourceBudgetUsage',
    'Resources',
    'SimState',
    # endpoints
    'ServiceAPI',
    'ServiceEndpointApi',
    'NodeApi',
    'NodeEndpointApi',
    'SimulationApi',
    'SimulationEndpointApi',
    'InterfaceApi',
    'InterfaceEndpointApi',
    'SimulationInterfaceApi',
    'SimulationNodeApi',
    'SystemEndpointAPI',
    'ImageApi',
    'ImageEndpointApi',
    'ImageShareEndpointAPI',
    'MarketplaceDemoEndpointAPI',
    'MarketplaceDemoApi',
    'MarketplaceDemoEndpointApi',
    'MarketplaceDemoTagEndpointAPI',
    'MarketplaceDemoTagApi',
    'SSHKey',
    'SSHKeyEndpointAPI',
    'UserConfigAPI',
    'UserConfigEndpointApi',
    # Organization / Resource Budget endpoint
    'Organization',
    'OrganizationApi',
    'OrganizationEndpointAPI',
    'ResourceBudget',
    'ResourceBudgetEndpointAPI',
]


from importlib.metadata import PackageNotFoundError, version

from air_sdk.exceptions import AirError, AirModelAttributeError
from air_sdk.types import (
    DockerRunParameters,
    DockerRunTmpfsParameter,
    EmulationParams,
    Platform,
    ResourceBudgetUsage,
    Resources,
    SimState,
)

if TYPE_CHECKING:
    from air_sdk.bc import CloudInitEndpointAPI
    from air_sdk.endpoints import (
        FleetEndpointAPI,
        HistoryEndpointAPI,
        ImageEndpointAPI,
        ImageShareEndpointAPI,
        InterfaceEndpointAPI,
        ManifestEndpointAPI,
        MarketplaceDemoEndpointAPI,
        MarketplaceDemoTagEndpointAPI,
        NodeEndpointAPI,
        NodeInstructionEndpointAPI,
        OrganizationEndpointAPI,
        ServiceEndpointAPI,
        SimulationEndpointAPI,
        SSHKeyEndpointAPI,
        SystemEndpointAPI,
        UserConfigEndpointAPI,
        WorkerClientCertificateEndpointAPI,
        WorkerEndpointAPI,
        ZTPScriptEndpointAPI,
    )


try:
    __version__ = version('nvidia-air-sdk')
except PackageNotFoundError:
    # package is not installed
    __version__ = 'unknown'


[docs] class AirApi: def __init__( self, api_url: str = const.AIR_API_URL, authenticate: bool = True, api_key: str | None = None, username: str | None = None, # BC parameter for BC init won't fail password: str | None = None, # BC alias for api_key bearer_token: str | None = None, # BC alias for api_key ) -> None: """ Initialize AirApi and optionally authenticate with a local NGC config. For cleaner initialization, consider using one of the factory methods: - AirApi.with_api_key() - AirApi.with_device_login() - AirApi.with_ngc_config() """ self.client = Client(api_url) if authenticate: # api_key, password, and bearer_token are all aliases for the same thing token = api_key or password or bearer_token if token: self.client.headers.update({'Authorization': f'Bearer {token}'}) else: self.auth_with_ngc_config()
[docs] @classmethod def with_api_key(cls, api_key: str, api_url: str = const.AIR_API_URL) -> 'AirApi': """Initialize API with an explicit NGC API Key. The `api_key` is also known as a Starfleet API Key, or 'SAK'. """ instance = cls(api_url=api_url, authenticate=False) instance.client.headers.update({'Authorization': f'Bearer {api_key}'}) return instance
[docs] @classmethod def with_device_login( cls, email: str, org_num: str, api_url: str = const.AIR_API_URL ) -> 'AirApi': """Initialize API with device login authentication.""" instance = cls(api_url=api_url, authenticate=False) instance.client.ngc_device_login(email, org_num) return instance
[docs] @classmethod def with_ngc_config(cls, api_url: str = const.AIR_API_URL) -> 'AirApi': """Initialize API using NGC config for authentication.""" return cls(api_url=api_url, authenticate=True)
[docs] def auth_with_ngc_config(self) -> None: """Authenticate with a local NGC config. This method is called by the `AirApi` constructor if `authenticate` is `True`. """ err_msg = ( 'No NGC API key found. Please run the CLI command `ngc config set` ' 'to set NGC API key on your local machine. Alternatively, ' 'use an AirApi.with_api_key(api_key) or ' 'AirApi.with_device_login(email, org_num) ' 'methods to explicitly authenticate your client.' ) try: auto_sak = self.client.hunt_for_sak() if auto_sak: print(f'Using auto-detected SAK found at ~/.ngc/config: {auto_sak}') # noqa: T201 self.client.headers.update({'Authorization': f'Bearer {auto_sak}'}) else: raise AirError(err_msg) except AirError: raise AirError(err_msg)
@property def histories(self) -> HistoryEndpointAPI: from .endpoints import HistoryEndpointAPI return HistoryEndpointAPI(self) @property def images(self) -> ImageEndpointAPI: from .endpoints import ImageEndpointAPI return ImageEndpointAPI(self) @property def image_shares(self) -> ImageShareEndpointAPI: from .endpoints import ImageShareEndpointAPI return ImageShareEndpointAPI(self) @property def simulations(self) -> SimulationEndpointAPI: from .endpoints import SimulationEndpointAPI return SimulationEndpointAPI(self) @property def systems(self) -> SystemEndpointAPI: from .endpoints import SystemEndpointAPI return SystemEndpointAPI(self) @property def nodes(self) -> NodeEndpointAPI: from .endpoints import NodeEndpointAPI return NodeEndpointAPI(self) @property def interfaces(self) -> InterfaceEndpointAPI: from .endpoints import InterfaceEndpointAPI return InterfaceEndpointAPI(self) @property def services(self) -> ServiceEndpointAPI: from .endpoints import ServiceEndpointAPI return ServiceEndpointAPI(self) @property @deprecated('simulation_interfaces is deprecated. Use AirApi.interfaces instead.') def simulation_interfaces(self) -> InterfaceEndpointAPI: return self.interfaces @property def node_instructions(self) -> NodeInstructionEndpointAPI: from .endpoints import NodeInstructionEndpointAPI return NodeInstructionEndpointAPI(self) @property @deprecated('simulation_nodes is deprecated, use AirApi.nodes instead.') def simulation_nodes(self) -> NodeEndpointAPI: return self.nodes @property def ztp_scripts(self) -> ZTPScriptEndpointAPI: from .endpoints import ZTPScriptEndpointAPI return ZTPScriptEndpointAPI(self) @property def workers(self) -> WorkerEndpointAPI: from .endpoints import WorkerEndpointAPI return WorkerEndpointAPI(self) @property def worker_client_certificates(self) -> WorkerClientCertificateEndpointAPI: from .endpoints import WorkerClientCertificateEndpointAPI return WorkerClientCertificateEndpointAPI(self) @property def fleets(self) -> FleetEndpointAPI: from .endpoints import FleetEndpointAPI return FleetEndpointAPI(self) @property def marketplace_demos(self) -> MarketplaceDemoEndpointAPI: from .endpoints import MarketplaceDemoEndpointAPI return MarketplaceDemoEndpointAPI(self) @property def marketplace_demo_tags(self) -> MarketplaceDemoTagEndpointAPI: from .endpoints import MarketplaceDemoTagEndpointAPI return MarketplaceDemoTagEndpointAPI(self) @property def ssh_keys(self) -> SSHKeyEndpointAPI: from .endpoints import SSHKeyEndpointAPI return SSHKeyEndpointAPI(self) @property def manifests(self) -> ManifestEndpointAPI: from .endpoints import ManifestEndpointAPI return ManifestEndpointAPI(self) @property @deprecated( 'The cloud_inits property is deprecated and left only for ' 'backwards compatibility. The correct way to interact with cloud-init ' 'is to use the bulk_assign property on the Simulation object.' ) def cloud_inits(self) -> CloudInitEndpointAPI: """Cloud-init endpoint for V2 backwards compatibility.""" from .bc import CloudInitEndpointAPI return CloudInitEndpointAPI(self) @property def user_configs(self) -> UserConfigEndpointAPI: from .endpoints import UserConfigEndpointAPI return UserConfigEndpointAPI(self) @property def organizations(self) -> OrganizationEndpointAPI: """Organization / Resource Budget endpoint.""" from .endpoints import OrganizationEndpointAPI return OrganizationEndpointAPI(self) @property def resource_budgets(self) -> OrganizationEndpointAPI: """Resource Budget endpoint (alias for organizations).""" return self.organizations # Deprecated singular aliases (v1/v2 backward compatibility) # TODO: Remove these properties in a future major version @property @deprecated( 'AirApi.simulation is deprecated and will be removed in a future version. ' 'Use AirApi.simulations instead.' ) def simulation(self) -> SimulationEndpointAPI: """Deprecated alias for simulations (v1 backward compatibility).""" return self.simulations @property @deprecated( 'AirApi.service is deprecated and will be removed in a future version. ' 'Use AirApi.services instead.' ) def service(self) -> ServiceEndpointAPI: """Deprecated alias for services (v1 backward compatibility).""" return self.services
[docs] def set_connect_timeout(self, t_delta: timedelta) -> None: self.client.connect_timeout = t_delta
[docs] def set_read_timeout(self, t_delta: timedelta) -> None: self.client.read_timeout = t_delta
[docs] def set_page_size(self, n: int) -> None: """Set the page size of paginated responses.""" if isinstance(n, int) and n > 0: self.client.pagination_page_size = n else: raise AirError('Pagination page size must be a positive integer.')
# Backward compatibility aliases (defined after AirApi to avoid circular imports) from air_sdk.endpoints import ( # noqa: E402 ImageEndpointAPI, InterfaceEndpointAPI, MarketplaceDemoEndpointAPI, MarketplaceDemoTagEndpointAPI, NodeEndpointAPI, Organization, OrganizationEndpointAPI, ResourceBudget, # alias for Organization ResourceBudgetEndpointAPI, # alias for OrganizationEndpointAPI ServiceEndpointAPI, SimulationEndpointAPI, SSHKey, SSHKeyEndpointAPI, UserConfigEndpointAPI, ) SimulationApi = SimulationEndpointAPI # v1 BC alias SimulationEndpointApi = SimulationEndpointAPI # v2 BC alias ImageApi = ImageEndpointAPI # v1 BC alias ImageEndpointApi = ImageEndpointAPI # v2 BC alias ServiceAPI = ServiceEndpointAPI # v1 BC alias ServiceEndpointApi = ServiceEndpointAPI # v2 BC alias MarketplaceDemoApi = MarketplaceDemoEndpointAPI # v1 BC alias MarketplaceDemoEndpointApi = MarketplaceDemoEndpointAPI # v2 BC alias MarketplaceDemoTagApi = MarketplaceDemoTagEndpointAPI # v1 BC alias InterfaceApi = InterfaceEndpointAPI # v1 BC alias InterfaceEndpointApi = InterfaceEndpointAPI # v2 BC alias SimulationInterfaceApi = InterfaceEndpointAPI # v1 BC alias SSHKeyApi = SSHKeyEndpointAPI # v1 BC alias SimulationNodeApi = NodeEndpointAPI # v1 BC alias NodeEndpointApi = NodeEndpointAPI # v2 BC alias NodeApi = NodeEndpointAPI # v1 BC alias UserConfigAPI = UserConfigEndpointAPI # v1 BC alias UserConfigEndpointApi = UserConfigEndpointAPI # v2 BC alias OrganizationApi = OrganizationEndpointAPI # v1 BC alias # Main class backward compatibility alias AirAPI = AirApi # alias for current tests TODO: remove that after all tests are updated