Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/qiskit_serverless/core/clients/local_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

from qiskit_ibm_runtime import QiskitRuntimeService

from qiskit_serverless.core.config import Config
from qiskit_serverless.core.constants import (
DATA_PATH,
ENV_JOB_ID_GATEWAY,
Expand Down Expand Up @@ -70,7 +71,7 @@ def __init__(self):
>>> local = LocalClient()
"""
super().__init__("local-client")
self.in_test = os.getenv("IN_TEST")
self.in_test = Config.in_test()
self._jobs = {}
self._functions = LocalFunctionsStore(self)

Expand Down
56 changes: 23 additions & 33 deletions client/qiskit_serverless/core/clients/serverless_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,7 @@
from qiskit_ibm_runtime.accounts import AccountManager, Account
from qiskit_ibm_runtime.accounts.exceptions import InvalidAccountError

from qiskit_serverless.core.constants import (
REQUESTS_TIMEOUT,
ENV_GATEWAY_PROVIDER_HOST,
ENV_GATEWAY_PROVIDER_VERSION,
ENV_GATEWAY_PROVIDER_TOKEN,
GATEWAY_PROVIDER_VERSION_DEFAULT,
IBM_SERVERLESS_HOST_URL,
MAX_ARTIFACT_FILE_SIZE_MB,
)
from qiskit_serverless.core.config import Config
from qiskit_serverless.core.client import BaseClient
from qiskit_serverless.core.decorators import trace_decorator_factory
from qiskit_serverless.core.enums import Channel
Expand Down Expand Up @@ -116,15 +108,13 @@ def __init__( # pylint: disable=too-many-positional-arguments
channel: identifies the method to use to authenticate the user
"""
name = name or "gateway-client"
host = host or os.environ.get(ENV_GATEWAY_PROVIDER_HOST)
host = host or Config.gateway_host()
if host is None:
raise QiskitServerlessException("Please provide `host` of gateway.")

version = version or os.environ.get(ENV_GATEWAY_PROVIDER_VERSION)
if version is None:
version = GATEWAY_PROVIDER_VERSION_DEFAULT
version = version or Config.gateway_provider_version()

token = token or os.environ.get(ENV_GATEWAY_PROVIDER_TOKEN)
token = token or Config.gateway_token()
if token is None:
raise QiskitServerlessException(
"Authentication credentials must be provided in form of `token`."
Expand Down Expand Up @@ -170,7 +160,7 @@ def _verify_credentials(self):
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
except QiskitServerlessException as reason:
Expand All @@ -184,7 +174,7 @@ def dependencies_versions(self):
request=lambda: requests.get(
url=f"{self.host}/api/{self.version}/dependencies-versions/",
headers=get_headers(token=self.token, instance=self.instance),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -229,7 +219,7 @@ def jobs(self, function: Optional[QiskitFunction] = None, **kwargs) -> List[Job]
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -281,7 +271,7 @@ def provider_jobs(self, function: Optional[QiskitFunction], **kwargs) -> List[Jo
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand All @@ -299,7 +289,7 @@ def job(self, job_id: str) -> Optional[Job]:
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -351,7 +341,7 @@ def run(
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
job_id = response_data.get("id")
Expand All @@ -369,7 +359,7 @@ def status(self, job_id: str):
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -404,7 +394,7 @@ def stop(self, job_id: str, service: Optional[QiskitRuntimeService] = None):
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
json=data,
)
)
Expand All @@ -420,7 +410,7 @@ def result(self, job_id: str):
token=self.token, instance=self.instance, channel=self.channel
),
params={"with_result": "true"},
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
return json.loads(
Expand All @@ -435,7 +425,7 @@ def logs(self, job_id: str):
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
return response_data.get("logs")
Expand All @@ -452,7 +442,7 @@ def runtime_jobs(
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand All @@ -474,7 +464,7 @@ def runtime_sessions(self, job_id: str):
headers=get_headers(
token=self.token, instance=self.instance, channel=self.channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
runtime_jobs = response_data.get("runtime_jobs", [])
Expand Down Expand Up @@ -557,7 +547,7 @@ def functions(self, **kwargs) -> List[RunnableQiskitFunction]:
token=self.token, instance=self.instance, channel=self.channel
),
params=kwargs,
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -585,7 +575,7 @@ def function(
token=self.token, instance=self.instance, channel=self.channel
),
params={"provider": provider},
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)

Expand Down Expand Up @@ -714,7 +704,7 @@ def __init__(
channel=self.account.channel,
token=self.account.token,
instance=self.account.instance,
host=host if host else IBM_SERVERLESS_HOST_URL,
host=host if host else Config.ibm_serverless_host_url(),
)

def _discover_account(
Expand Down Expand Up @@ -843,7 +833,7 @@ def _upload_with_docker_image( # pylint: disable=too-many-positional-arguments
"description": program.description,
},
headers=get_headers(token=token, instance=instance, channel=channel),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
program_title = response_data.get("title", "na")
Expand Down Expand Up @@ -898,10 +888,10 @@ def _upload_with_artifact( # pylint: disable=too-many-positional-arguments, to

# check file size
size_in_mb = Path(artifact_file_path).stat().st_size / 1024**2
if size_in_mb > MAX_ARTIFACT_FILE_SIZE_MB:
if size_in_mb > Config.max_artifact_file_size_mb():
raise QiskitServerlessException(
f"{artifact_file_path} is {int(size_in_mb)} Mb, "
f"which is greater than {MAX_ARTIFACT_FILE_SIZE_MB} allowed. "
f"which is greater than {Config.max_artifact_file_size_mb()} allowed. "
f"Try to reduce size of `working_dir`."
)

Expand All @@ -922,7 +912,7 @@ def _upload_with_artifact( # pylint: disable=too-many-positional-arguments, to
headers=get_headers(
token=token, instance=instance, channel=channel
),
timeout=REQUESTS_TIMEOUT,
timeout=Config.requests_timeout(),
)
)
span.set_attribute("function.title", response_data.get("title", "na"))
Expand Down
154 changes: 154 additions & 0 deletions client/qiskit_serverless/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""Centralized configuration for environment variables."""

import os
from typing import Optional


class Config: # pylint: disable=too-many-public-methods
"""Centralized configuration for environment variables.

This class provides type-safe access to environment variables with
consistent default values across the codebase.
"""

# Telemetry/Tracing Configuration
@classmethod
def ot_program_name(cls) -> str:
"""Get OpenTelemetry program name."""
return os.environ.get("OT_PROGRAM_NAME", "unnamed_execution")

@classmethod
def ot_jaeger_host(cls) -> Optional[str]:
"""Get Jaeger host for tracing."""
return os.environ.get("OT_JAEGER_HOST_KEY", None)

@classmethod
def ot_jaeger_port(cls) -> int:
"""Get Jaeger port for tracing."""
return int(os.environ.get("OT_JAEGER_PORT_KEY", "6831"))

@classmethod
def ot_traceparent_id(cls) -> Optional[str]:
"""Get OpenTelemetry traceparent ID."""
return os.environ.get("OT_TRACEPARENT_ID_KEY", None)

@classmethod
def ot_insecure(cls) -> bool:
"""Check if OpenTelemetry should use insecure connection."""
return bool(int(os.environ.get("OT_INSECURE", "0")))

@classmethod
def ot_enabled(cls) -> bool:
"""Check if OpenTelemetry tracing is enabled."""
return bool(int(os.environ.get("OT_ENABLED", "0")))

@classmethod
def ot_ray_tracer(cls) -> bool:
"""Check if Ray tracer is enabled."""
return bool(int(os.environ.get("OT_RAY_TRACER", "0")))

# Gateway Configuration
@classmethod
def gateway_host(cls) -> Optional[str]:
"""Get gateway provider host."""
return os.environ.get("ENV_GATEWAY_PROVIDER_HOST", None)

@classmethod
def gateway_version(cls) -> str:
"""Get gateway provider version."""
return os.environ.get("ENV_GATEWAY_PROVIDER_VERSION", "v1")

@classmethod
def gateway_provider_version(cls) -> str:
"""Get gateway provider version (alias for gateway_version)."""
return cls.gateway_version()
Comment on lines +57 to +64
Copy link
Contributor

@avilches avilches Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have two methods (where one calls the other) instead of just one?


@classmethod
def gateway_token(cls) -> Optional[str]:
"""Get gateway provider token."""
return os.environ.get("ENV_GATEWAY_PROVIDER_TOKEN", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the "provider" part removed from the gateway host, token and version? I mean, why not gateway_provider_token, gateway_provider_version and gateway_provider_host instead... is the "provider" word redundant in the gateway?


# Job Configuration
@classmethod
def job_gateway_token(cls) -> Optional[str]:
"""Get job gateway token."""
return os.environ.get("ENV_JOB_GATEWAY_TOKEN", None)

@classmethod
def job_gateway_instance(cls) -> Optional[str]:
"""Get job gateway instance."""
return os.environ.get("ENV_JOB_GATEWAY_INSTANCE", None)

@classmethod
def job_gateway_host(cls) -> Optional[str]:
"""Get job gateway host."""
return os.environ.get("ENV_JOB_GATEWAY_HOST", None)

@classmethod
def job_id_gateway(cls) -> Optional[str]:
"""Get job ID from gateway."""
return os.environ.get("ENV_JOB_ID_GATEWAY", None)

@classmethod
def qiskit_ibm_channel(cls) -> Optional[str]:
"""Get Qiskit IBM channel."""
return os.environ.get("QISKIT_IBM_CHANNEL", None)

@classmethod
def qiskit_ibm_token(cls) -> Optional[str]:
"""Get Qiskit IBM token."""
return os.environ.get("QISKIT_IBM_TOKEN", None)

@classmethod
def qiskit_ibm_url(cls) -> Optional[str]:
"""Get Qiskit IBM URL."""
return os.environ.get("QISKIT_IBM_URL", None)

@classmethod
def qiskit_ibm_instance(cls) -> Optional[str]:
"""Get Qiskit IBM instance."""
return os.environ.get("QISKIT_IBM_INSTANCE", None)

# Data Configuration
@classmethod
def data_path(cls) -> str:
"""Get data path."""
return os.environ.get("DATA_PATH", "/data")

# Request Configuration
@classmethod
def requests_timeout(cls) -> int:
"""Get request timeout in seconds."""
return int(os.environ.get("REQUESTS_TIMEOUT_OVERRIDE", "30"))

@classmethod
def requests_streaming_timeout(cls) -> int:
"""Get streaming request timeout in seconds."""
return int(os.environ.get("REQUESTS_TIMEOUT_OVERRIDE", "60"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two config values have the same environment variable REQUESTS_TIMEOUT_OVERRIDE


# Artifact Configuration
@classmethod
def max_artifact_file_size_mb(cls) -> int:
"""Get maximum artifact file size in MB."""
return int(os.environ.get("MAX_ARTIFACT_FILE_SIZE_MB_OVERRIDE", "50"))

# IBM Serverless Configuration
@classmethod
def ibm_serverless_host_url(cls) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should be ibm_serverless_host_url_override

"""Get IBM Serverless host URL."""
return os.environ.get(
"IBM_SERVERLESS_HOST_URL_OVERRIDE",
"https://qiskit-serverless.quantum.ibm.com",
)

# Access Configuration
@classmethod
def is_trial(cls) -> bool:
"""Check if running in trial mode."""
return os.environ.get("ENV_ACCESS_TRIAL") == "True"

# Test Configuration
@classmethod
def in_test(cls) -> Optional[str]:
"""Check if running in test mode."""
return os.environ.get("IN_TEST")
Loading