Skip to content

API Client

The EpiSuiteAPIClient class provides the core interface for interacting with the EPISuite API.

By default, the client now prefers a local runtime when data/local/EpiSuiteCLI.jar is available. Set PYEPISUITE_MODE=remote to force the hosted API.

Overview

pyepisuite.api_client.EpiSuiteAPIClient

Source code in src/pyepisuite/api_client.py
class EpiSuiteAPIClient:
    def __init__(self, base_url=None, api_key=None):
        mode = os.getenv('PYEPISUITE_MODE', 'auto').strip().lower()
        if mode not in {'auto', 'local', 'remote'}:
            raise ValueError("PYEPISUITE_MODE must be one of: auto, local, remote")

        resolved_base_url = base_url
        self.local_mode = False

        if resolved_base_url is None:
            explicit_local_base = os.getenv('PYEPISUITE_LOCAL_BASE_URL')

            if mode == 'remote':
                resolved_base_url = DEFAULT_REMOTE_BASE_URL
            elif explicit_local_base:
                resolved_base_url = _with_api_suffix(explicit_local_base)
                self.local_mode = True
            elif mode == 'local' or _LocalRuntimeManager.has_local_assets():
                resolved_base_url = _with_api_suffix(_LocalRuntimeManager.ensure_started())
                self.local_mode = True
            else:
                resolved_base_url = DEFAULT_REMOTE_BASE_URL
        elif re.match(r'^https?://(127\.0\.0\.1|localhost)(:\d+)?(/.*)?$', resolved_base_url):
            self.local_mode = True

        self.base_url = resolved_base_url
        self.api_key = api_key

    def _headers(self):
        headers = {}
        if self.api_key:
            headers['Authorization'] = f'Bearer {self.api_key}'
        return headers

    def _parse_json_response(self, response, operation_name):
        response.raise_for_status()
        content_type = response.headers.get('content-type', '').lower()
        if 'application/json' not in content_type:
            snippet = response.text[:600].strip()
            raise ValueError(
                f'{operation_name} expected JSON but received {content_type or "unknown content type"}. '
                f'Response snippet: {snippet}'
            )
        return response.json()

    def search(self, query_term, time_out=10):
        """
        Search the EPISuite API with a query term (SMILES, CAS, or chemical name).

        Parameters:
            query_term (str): The term to search for.
            time_out (int): The time out for the request.

        Returns:
            List[Chemical]: A list of Chemical instances.
        """
        url = f'{self.base_url}/search'
        params = {'query': query_term}
        response = requests.get(url, params=params, headers=self._headers(), timeout=time_out)
        data = self._parse_json_response(response, 'search')

        # Convert each dictionary in the response to a Chemical instance
        ids = [Identifiers(**item) for item in data]
        return ids

    def submit(self, cas="", smiles=""):
        """
        Submit a CAS number or SMILES string to the EPISuite API.

        Parameters:
            cas (str): The CAS number of the chemical.
            smiles (str): The SMILES string of the chemical.

        Returns:
            dict: The JSON response from the API.

        Raises:
            ValueError: If neither 'cas' nor 'smiles' is provided.
        """
        if not cas and not smiles:
            raise ValueError("Either 'cas' or 'smiles' must be provided.")

        url = f'{self.base_url}/submit'
        params = {}
        if cas:
            params['cas'] = cas
        else:
            params['smiles'] = smiles

        response = requests.get(url, params=params, headers=self._headers(), timeout=120)
        try:
            return self._parse_json_response(response, 'submit')
        except ValueError as exc:
            if cas and self.local_mode and _is_cas_formatted(cas):
                normalized = _normalize_cas_for_local(cas)
                if normalized != cas:
                    retry_params = dict(params)
                    retry_params['cas'] = normalized
                    retry_response = requests.get(url, params=retry_params, headers=self._headers(), timeout=120)
                    return self._parse_json_response(retry_response, 'submit')
            raise exc

    @staticmethod
    def stop_local_runtime():
        """Stop the managed local runtime launched by this package."""
        stop_local_episuite_server()

search(query_term, time_out=10)

Search the EPISuite API with a query term (SMILES, CAS, or chemical name).

Parameters:

Name Type Description Default
query_term str

The term to search for.

required
time_out int

The time out for the request.

10

Returns:

Type Description

List[Chemical]: A list of Chemical instances.

Source code in src/pyepisuite/api_client.py
def search(self, query_term, time_out=10):
    """
    Search the EPISuite API with a query term (SMILES, CAS, or chemical name).

    Parameters:
        query_term (str): The term to search for.
        time_out (int): The time out for the request.

    Returns:
        List[Chemical]: A list of Chemical instances.
    """
    url = f'{self.base_url}/search'
    params = {'query': query_term}
    response = requests.get(url, params=params, headers=self._headers(), timeout=time_out)
    data = self._parse_json_response(response, 'search')

    # Convert each dictionary in the response to a Chemical instance
    ids = [Identifiers(**item) for item in data]
    return ids

stop_local_runtime() staticmethod

Stop the managed local runtime launched by this package.

Source code in src/pyepisuite/api_client.py
@staticmethod
def stop_local_runtime():
    """Stop the managed local runtime launched by this package."""
    stop_local_episuite_server()

submit(cas='', smiles='')

Submit a CAS number or SMILES string to the EPISuite API.

Parameters:

Name Type Description Default
cas str

The CAS number of the chemical.

''
smiles str

The SMILES string of the chemical.

''

Returns:

Name Type Description
dict

The JSON response from the API.

Raises:

Type Description
ValueError

If neither 'cas' nor 'smiles' is provided.

Source code in src/pyepisuite/api_client.py
def submit(self, cas="", smiles=""):
    """
    Submit a CAS number or SMILES string to the EPISuite API.

    Parameters:
        cas (str): The CAS number of the chemical.
        smiles (str): The SMILES string of the chemical.

    Returns:
        dict: The JSON response from the API.

    Raises:
        ValueError: If neither 'cas' nor 'smiles' is provided.
    """
    if not cas and not smiles:
        raise ValueError("Either 'cas' or 'smiles' must be provided.")

    url = f'{self.base_url}/submit'
    params = {}
    if cas:
        params['cas'] = cas
    else:
        params['smiles'] = smiles

    response = requests.get(url, params=params, headers=self._headers(), timeout=120)
    try:
        return self._parse_json_response(response, 'submit')
    except ValueError as exc:
        if cas and self.local_mode and _is_cas_formatted(cas):
            normalized = _normalize_cas_for_local(cas)
            if normalized != cas:
                retry_params = dict(params)
                retry_params['cas'] = normalized
                retry_response = requests.get(url, params=retry_params, headers=self._headers(), timeout=120)
                return self._parse_json_response(retry_response, 'submit')
        raise exc

Usage Examples

Basic Client Setup

from pyepisuite import EpiSuiteAPIClient

# Create client with default settings.
# If a local JAR is present, this starts local mode automatically.
client = EpiSuiteAPIClient()

# Or with custom base URL
client = EpiSuiteAPIClient(base_url='https://episuite.dev/EpiWebSuite/api')

# With API key (if required)
client = EpiSuiteAPIClient(api_key='your-api-key')

Explicit Local Client

from pyepisuite import LocalEpiSuiteAPIClient, stop_local_episuite_server

# Always target local runtime.
client = LocalEpiSuiteAPIClient()

ids = client.search('formaldehyde')
result = client.submit(cas='000050-00-0')

# Stop managed local process when needed.
stop_local_episuite_server()

Mode Selection

Use PYEPISUITE_MODE to control behavior:

  • auto (default): prefer local runtime if JAR exists, otherwise use remote API
  • local: require local runtime and fail fast if startup fails
  • remote: always use hosted API

Optional local environment variables:

  • PYEPISUITE_LOCAL_JAR_PATH: absolute/relative path to EpiSuiteCLI.jar
  • PYEPISUITE_LOCAL_BASE_URL: connect-only mode, e.g. http://127.0.0.1:45511
  • PYEPISUITE_LOCAL_STARTUP_TIMEOUT: startup timeout in seconds (default 60)

Searching for Chemicals

# Search by CAS number
results = client.search('50-00-0')

# Search by SMILES
results = client.search('C=O')

# Search by chemical name
results = client.search('formaldehyde')

Submitting for Predictions

# Submit using CAS number
predictions = client.submit(cas='50-00-0')

# Submit using SMILES
predictions = client.submit(smiles='C=O')

# The response includes both EPI Suite and EcoSAR results
print(predictions.keys())  # ['chemicalProperties', 'logKow', ..., 'ecosar']

Error Handling

The API client includes robust error handling:

import requests

try:
    results = client.search('invalid-identifier')
except requests.exceptions.RequestException as e:
    print(f"API request failed: {e}")
except ValueError as e:
    print(f"Invalid parameters: {e}")

Configuration Options

Timeout Settings

# Custom timeout for slow networks
results = client.search('50-00-0', time_out=30)

API Key Authentication

If the API requires authentication:

client = EpiSuiteAPIClient(api_key='your-api-key-here')

For higher-level operations, see the utility functions: