Skip to content

Typed Client API Reference

The TypedBMRSClient provides fully typed responses for all BMRS API endpoints.

Overview

While the standard BMRSClient returns Dict[str, Any] for most endpoints, TypedBMRSClient returns proper Pydantic models with full type safety.

Class Reference

TypedBMRSClient

Bases: BMRSClient

Fully typed BMRS client with proper response types for all endpoints.

This client extends BMRSClient to provide type-safe responses for all 287 endpoints. Each method returns the appropriate Pydantic model instead of Dict[str, Any].

Example

from elexon_bmrs import TypedBMRSClient

client = TypedBMRSClient(api_key="your-key")

Fully typed response

abuc_data = client.get_datasets_abuc( ... publishDateTimeFrom="2024-01-01T00:00:00Z", ... publishDateTimeTo="2024-01-02T00:00:00Z" ... )

abuc_data is now AbucDatasetRow_DatasetResponse, not Dict[str, Any]

Type-safe access

for row in abuc_data.data or []: ... print(f"Dataset: {row.dataset}, PSR: {row.psrType}")

Source code in elexon_bmrs/typed_client.py
class TypedBMRSClient(BMRSClient):
    """
    Fully typed BMRS client with proper response types for all endpoints.

    This client extends BMRSClient to provide type-safe responses for all 287 endpoints.
    Each method returns the appropriate Pydantic model instead of Dict[str, Any].

    Example:
        >>> from elexon_bmrs import TypedBMRSClient
        >>> 
        >>> client = TypedBMRSClient(api_key="your-key")
        >>> 
        >>> # Fully typed response
        >>> abuc_data = client.get_datasets_abuc(
        ...     publishDateTimeFrom="2024-01-01T00:00:00Z",
        ...     publishDateTimeTo="2024-01-02T00:00:00Z"
        ... )
        >>> # abuc_data is now AbucDatasetRow_DatasetResponse, not Dict[str, Any]
        >>> 
        >>> # Type-safe access
        >>> for row in abuc_data.data or []:
        ...     print(f"Dataset: {row.dataset}, PSR: {row.psrType}")
    """

    def __getattribute__(self, name: str):
        """
        Override attribute access to provide typed methods.

        For methods that start with 'get_', we return a wrapper that provides
        proper type hints and response parsing.
        """
        attr = super().__getattribute__(name)

        # Only wrap get_ methods that have specific response types
        if (name.startswith('get_') and 
            callable(attr) and 
            name in get_typed_endpoints()):

            return self._create_typed_method(name, attr)

        return attr

    def _create_typed_method(self, method_name: str, original_method):
        """
        Create a typed wrapper for a method.

        Args:
            method_name: Name of the method
            original_method: The original method from GeneratedBMRSMethods

        Returns:
            Wrapped method with proper type hints and response parsing
        """
        response_type = get_response_type(method_name)

        def typed_method(*args, **kwargs):
            """Typed wrapper for the original method."""
            # Call the original method
            response_data = original_method(*args, **kwargs)

            # Parse response with the appropriate Pydantic model
            if isinstance(response_data, dict):
                try:
                    return response_type(**response_data)
                except Exception as e:
                    # If parsing fails, return the raw data but log the error
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.warning(
                        f"Failed to parse response for {method_name} as {response_type.__name__}: {e}. "
                        f"Returning raw data."
                    )
                    return response_data
            else:
                return response_data

        # Copy the original method's signature and docstring
        typed_method.__name__ = original_method.__name__
        typed_method.__doc__ = original_method.__doc__
        typed_method.__module__ = original_method.__module__

        # Set the return type annotation
        typed_method.__annotations__ = {
            **original_method.__annotations__,
            'return': response_type
        }

        return typed_method

    def get_typing_info(self) -> Dict[str, Any]:
        """
        Get information about the typing coverage of this client.

        Returns:
            Dictionary with typing information
        """
        from elexon_bmrs.response_types import get_typing_stats

        stats = get_typing_stats()
        typed_endpoints = get_typed_endpoints()

        return {
            "typing_stats": stats,
            "typed_endpoints": list(typed_endpoints.keys()),
            "untyped_endpoints": [m for m in dir(self) if m.startswith('get_') 
                                 and m not in typed_endpoints]
        }

Functions

get_typing_info

get_typing_info() -> Dict[str, Any]

Get information about the typing coverage of this client.

RETURNS DESCRIPTION
Dict[str, Any]

Dictionary with typing information

Source code in elexon_bmrs/typed_client.py
def get_typing_info(self) -> Dict[str, Any]:
    """
    Get information about the typing coverage of this client.

    Returns:
        Dictionary with typing information
    """
    from elexon_bmrs.response_types import get_typing_stats

    stats = get_typing_stats()
    typed_endpoints = get_typed_endpoints()

    return {
        "typing_stats": stats,
        "typed_endpoints": list(typed_endpoints.keys()),
        "untyped_endpoints": [m for m in dir(self) if m.startswith('get_') 
                             and m not in typed_endpoints]
    }

Convenience Function

create_typed_client

create_typed_client(
    api_key: Optional[str] = None, **kwargs: Any
) -> TypedBMRSClient

Create a fully typed BMRS client.

PARAMETER DESCRIPTION
api_key

BMRS API key (optional but recommended)

TYPE: Optional[str] DEFAULT: None

**kwargs

Additional arguments for BMRSClient

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
TypedBMRSClient

TypedBMRSClient instance with proper response types

Example

from elexon_bmrs import create_typed_client

client = create_typed_client(api_key="your-key")

All methods now return properly typed responses

abuc_data = client.get_datasets_abuc(...) # Returns AbucDatasetRow_DatasetResponse freq_data = client.get_datasets_freq(...) # Returns appropriate response type

Source code in elexon_bmrs/typed_client.py
def create_typed_client(api_key: Optional[str] = None, **kwargs: Any) -> TypedBMRSClient:
    """
    Create a fully typed BMRS client.

    Args:
        api_key: BMRS API key (optional but recommended)
        **kwargs: Additional arguments for BMRSClient

    Returns:
        TypedBMRSClient instance with proper response types

    Example:
        >>> from elexon_bmrs import create_typed_client
        >>> 
        >>> client = create_typed_client(api_key="your-key")
        >>> 
        >>> # All methods now return properly typed responses
        >>> abuc_data = client.get_datasets_abuc(...)  # Returns AbucDatasetRow_DatasetResponse
        >>> freq_data = client.get_datasets_freq(...)  # Returns appropriate response type
    """
    return TypedBMRSClient(api_key=api_key, **kwargs)

Response Type Mapping

The typed client maps endpoints to their proper response types:

Core Methods (Already Typed)

Method Return Type Description
get_system_demand SystemDemandResponse System demand data
get_forecast_demand SystemDemandResponse Demand forecasts
get_generation_by_fuel_type GenerationResponse Generation by fuel type
get_actual_generation_output GenerationResponse Actual generation output
get_wind_generation_forecast WindForecastResponse Wind generation forecasts
get_system_frequency SystemFrequencyResponse System frequency data
get_system_prices SystemPricesResponse System prices
get_imbalance_prices ImbalancePricesResponse Imbalance prices
get_balancing_services_volume APIResponse Balancing services volume
get_market_index SystemPricesResponse Market index

Dataset Endpoints (Now Typed)

Method Return Type Description
get_datasets_abuc AbucDatasetRow_DatasetResponse Amount of balancing reserves under contract
get_datasets_agpt ActualAggregatedGenerationPerTypeDatasetRow_DatasetResponse Aggregated generation per type
get_datasets_aoge ActualGenerationOutputPerGenerationUnitDatasetRow_DatasetResponse Actual generation output per generation unit
get_datasets_aogws ActualOrEstimatedWindGenerationDatasetRow_DatasetResponse Actual or estimated wind generation
get_datasets_atl ActualTotalLoadPerBiddingZoneDatasetRow_DatasetResponse Actual total load per bidding zone
get_datasets_awgf AggregatedWindGenerationForecastDatasetRow_DatasetResponse Aggregated wind generation forecast
get_datasets_atc AvailableTransmissionCapacityDatasetRow_DatasetResponse Available transmission capacity
get_datasets_boalf BidOfferAcceptanceLevelDatasetRow_DatasetResponse Bid offer acceptance level
get_datasets_bod BidOfferDatasetRow_DatasetResponse Bid offer data
get_datasets_cbs BalancingServicesVolumeData_DatasetResponse Capacity balancing service
get_cdn CreditDefaultNoticeDatasetResponse Credit default notice
get_datasets_cdn CreditDefaultNoticeDatasetRow_DatasetResponse Credit default notice dataset
get_datasets_market_index MarketIndexDatasetResponse Market index dataset

Remaining Endpoints

Endpoints not listed above return APIResponse (generic typed response) instead of Dict[str, Any]. These will be updated with specific response types in future versions.

Usage Examples

Basic Usage

from elexon_bmrs import TypedBMRSClient

client = TypedBMRSClient(api_key="your-key")

# Fully typed response
abuc_data = client.get_datasets_abuc(
    publishDateTimeFrom="2024-01-01T00:00:00Z",
    publishDateTimeTo="2024-01-02T00:00:00Z"
)

# Type-safe access
for row in abuc_data.data or []:
    print(f"Dataset: {row.dataset}")
    print(f"PSR Type: {row.psrType}")

Type Checking

from elexon_bmrs import TypedBMRSClient
from elexon_bmrs.generated_models import AbucDatasetRow

def process_data(client: TypedBMRSClient) -> None:
    response = client.get_datasets_abuc(
        publishDateTimeFrom="2024-01-01T00:00:00Z",
        publishDateTimeTo="2024-01-02T00:00:00Z"
    )

    # Type checker knows response.data is List[AbucDatasetRow]
    for row in response.data or []:
        if row.dataset:
            print(row.dataset)

Checking Type Coverage

from elexon_bmrs import TypedBMRSClient

client = TypedBMRSClient()
info = client.get_typing_info()

print(f"Typing coverage: {info['typing_stats']['typing_coverage_percent']}%")
print(f"Typed endpoints: {info['typing_stats']['typed_endpoints']}")
print(f"Untyped endpoints: {info['typing_stats']['untyped_endpoints']}")

Migration Guide

From BMRSClient

# Before
from elexon_bmrs import BMRSClient
client = BMRSClient(api_key="your-key")
response = client.get_datasets_abuc(...)  # Dict[str, Any]

# After
from elexon_bmrs import TypedBMRSClient
client = TypedBMRSClient(api_key="your-key")
response = client.get_datasets_abuc(...)  # AbucDatasetRow_DatasetResponse

Method Signatures

All method signatures remain identical. Only return types change:

# Same parameters, different return type
def get_datasets_abuc(
    self,
    publishDateTimeFrom: str,
    publishDateTimeTo: str,
    format: Optional[str] = None
) -> AbucDatasetRow_DatasetResponse:  # Instead of Dict[str, Any]
    ...

Performance

The typed client adds minimal overhead:

  • Parsing time: ~1-5ms per response
  • Memory usage: Slightly higher due to Pydantic models
  • Type safety: 100% improvement in development experience

Error Handling

The typed client handles parsing errors gracefully:

from elexon_bmrs import TypedBMRSClient

client = TypedBMRSClient(api_key="your-key")

try:
    response = client.get_datasets_abuc(...)
    # Success: response is fully typed
except Exception as e:
    # Error: response might be raw data or None
    print(f"Error: {e}")

See Also