# Copyright (c) 2025, qBraid Development Team
# All rights reserved.
"""
Module defining abstract base clas for qBraid micro-service clients.
"""
from typing import Any, Optional
from ._compat import check_version
from .config import load_config
from .exceptions import AuthError, RequestsApiError, ResourceNotFoundError, UserNotFoundError
from .sessions import QbraidSessionV1
[docs]
class QbraidClientV1: # pylint: disable=too-few-public-methods
"""Base class for qBraid micro-service clients interfacing with the new
qBraid API."""
[docs]
def __init__(self, api_key: Optional[str] = None, session: Optional[QbraidSessionV1] = None):
if api_key and session:
raise ValueError("Provide either api_key or session, not both.")
self._user_metadata: Optional[dict[str, str]] = None
self.session = session or QbraidSessionV1(api_key=api_key)
check_version("qbraid-core")
@property
def session(self) -> QbraidSessionV1:
"""The QbraidSessionV1 used to make requests."""
return self._session
@session.setter
def session(self, value: Optional[QbraidSessionV1]) -> None:
"""Set the QbraidSessionV1, ensuring it is a valid QbraidSessionV1 instance.
Raises:
AuthError: If the provided session is not valid.
TypeError: If the value is not a QbraidSessionV1 instance.
"""
session = value or QbraidSessionV1()
if not isinstance(session, QbraidSessionV1):
raise TypeError("The session must be a QbraidSessionV1 instance.")
try:
self._user_metadata = session.get_user_auth_metadata()
except UserNotFoundError as err:
raise AuthError(f"Access denied due to missing or invalid credentials: {err}") from err
self._session = session
@staticmethod
def running_in_lab() -> bool:
"""Check if running in the qBraid Lab environment.
Reads the 'cloud' setting from ~/.qbraid/qbraidrc config file.
Only the "default" section is supported. The "cloud" value must be
set to "true" (case-insensitive) for this method to return True.
Returns:
bool: True if running in qBraid Lab, False otherwise
"""
try:
config = load_config()
if config.has_option("default", "cloud"):
cloud_value = config.get("default", "cloud").strip().lower()
return cloud_value == "true"
except (FileNotFoundError, OSError, KeyError):
# If config file doesn't exist or can't be read, assume not on Lab
pass
return False
def get_credits_balance(self) -> dict[str, Any]:
"""Get the current user's credit balance for their organization.
The balance is tied to the organization associated with the API key
used for authentication.
Returns:
dict: Credit balance info with keys:
- qbraidCredits (float): qBraid credit balance
- awsCredits (float): AWS credit balance
- autoRecharge (str): Auto-recharge status
- organizationId (str): Organization ID
- userId (str): User ID
"""
assert self._user_metadata is not None
org_id = self._user_metadata.get("organizationId")
try:
res = self.session.get("/billing/credits/balance", params={"organizationId": org_id})
return res.json()["data"]
except RequestsApiError as err:
raise ResourceNotFoundError(f"Credits balance not found: {err}") from err
def user_credits_value(self) -> float:
"""Get the current user's qBraid credits value.
Returns:
float: The current user's qBraid credits value.
"""
try:
balance = self.get_credits_balance()
return float(balance["qbraidCredits"])
except (KeyError, ValueError) as err:
raise ResourceNotFoundError(f"Credits value not found: {err}") from err
class QbraidClient(QbraidClientV1):
"""Legacy client class for backwards compatibility."""