Client Preparation
Imports
#exports
from tqdm import tqdm
from warnings import warn
from functools import reduce
from ElexonDataPortal.dev import rawgen, specgen, raw, utils
from IPython.display import JSON
import pandas as pd
import os
from dotenv import load_dotenv
assert load_dotenv('../.env'), 'Environment variables could not be loaded'
api_key = os.environ['BMRS_API_KEY']
API_yaml = specgen.load_API_yaml(fp='../data/BMRS_API.yaml')
JSON(API_yaml)
<IPython.core.display.JSON object>
#exports
def test_endpoints(
default_kwargs: dict
):
methods_to_test = [func for func in dir(raw) if 'get_' in func]
stream_to_df = dict()
for method_to_test in tqdm(methods_to_test):
method_func = getattr(raw, method_to_test)
func_kwargs = dict(zip(method_func.__code__.co_varnames, method_func.__defaults__))
for kwarg, value in default_kwargs.items():
if kwarg in func_kwargs.keys():
func_kwargs.update({kwarg: value})
r = method_func(**func_kwargs)
df = utils.parse_xml_response(r)
stream_to_df[method_to_test.split('_')[1]] = df
streams_without_content = []
for stream, df in stream_to_df.items():
if df.size == 0:
streams_without_content += [stream]
return streams_without_content
if len(streams_without_content) > 0:
warn(f"The following data streams returned no content data: {', '.join(streams_without_content)}")
return stream_to_df
default_kwargs = {
'APIKey': api_key,
'ServiceType': 'xml'
}
stream_to_df = test_endpoints(default_kwargs)
100% 49/49 [00:35<00:00, 1.39it/s]
#exports
def construct_method_to_params_dict(API_yaml):
method_to_params = reduce(lambda k, v: {**k, **v}, [
{
f'{k}_{rawgen.clean_path_name(stream)}': {
parameter['name']: rawgen.extract_parameter_example(parameter)
for parameter
in v['parameters']
}
for k, v
in method.items()
if k in ['get', 'post']
}
for stream, method
in API_yaml['paths'].items()
])
return method_to_params
method_to_params = construct_method_to_params_dict(API_yaml)
pd.Series(method_to_params).head(3).to_dict()
{'get_B0610': {'APIKey': 'AP8DA23',
'SettlementDate': '2021-01-01',
'Period': '1',
'ServiceType': 'csv'},
'get_B0620': {'APIKey': 'AP8DA23',
'SettlementDate': '2021-01-01',
'Period': '1',
'ServiceType': 'csv'},
'get_B0630': {'APIKey': 'AP8DA23',
'Year': '2021',
'Week': '22',
'ServiceType': 'csv'}}
flatten_list = lambda list_: [item for sublist in list_ for item in sublist]
field_names = sorted(list(set(flatten_list([list(params.keys()) for params in method_to_params.values()]))))
field_names
['APIKey',
'ActiveFlag',
'AssetID',
'BMUnitId',
'BMUnitType',
'EndDate',
'EndTime',
'EventEnd',
'EventStart',
'EventType',
'FromClearedDate',
'FromDate',
'FromDateTime',
'FromSettlementDate',
'FuelType',
'LeadPartyName',
'MessageID',
'MessageId',
'MessageType',
'Month',
'NGCBMUnit',
'NGCBMUnitID',
'Name',
'ParticipantId',
'Period',
'ProcessType',
'PublicationFrom',
'PublicationTo',
'SequenceId',
'ServiceType',
'SettlementDate',
'SettlementPeriod',
'StartDate',
'StartTime',
'ToClearedDate',
'ToDate',
'ToDateTime',
'ToSettlementDate',
'UnavailabilityType',
'Week',
'Year',
'isTwoDayWindow']
#exports
def construct_request_type_filter(
has_start_time: bool,
has_end_time: bool,
has_start_date: bool,
has_end_date: bool,
has_date: bool,
has_SP: bool,
has_year: bool,
has_month: bool,
has_week: bool
):
request_type_filter = {
'year': (has_year + has_month + has_week == 1) and (has_year == 1),
'month': (has_year + has_month == 1) and (has_month == 1),
'week': (has_year + has_week == 1) and (has_week == 1),
'year_and_month': has_year + has_month == 2,
'year_and_week': has_year + has_week == 2,
'SP_and_date': has_SP + has_date == 2,
'date_range': has_start_time + has_end_time + has_start_date + has_end_date == 2,
'date_time_range': has_start_time + has_end_time + has_start_date + has_end_date == 4,
'non_temporal': has_start_time + has_end_time + has_start_date + has_end_date + has_SP + has_date + has_year + has_month == 0,
}
return request_type_filter
def check_request_type_filter(
field_names: list,
request_type_filter: dict,
has_start_time: bool,
has_end_time: bool,
has_start_date: bool,
has_end_date: bool,
has_date: bool,
has_SP: bool,
has_year: bool,
has_month: bool,
has_week: bool
):
"""
Checks the validity of the specified stream parameters
The following conditions will raise an error:
* has month without a year
* has only one of start/end time
* has only one of start/end date
* has only one settlement period or date
* filter does not contain only one request type
"""
filter_str = f'\n\nFilter:\n{request_type_filter}\n\nField Names:\n{", ".join(field_names)}'
assert {(False, True): True, (False, False): False, (True, True): False, (True, False): False}[(has_year, has_month)] == False, 'Cannot provide a month without a year' + filter_str
assert {(False, True): True, (False, False): False, (True, True): False, (True, False): False}[(has_year, has_week)] == False, 'Cannot provide a week without a year' + filter_str
assert has_start_time + has_end_time != 1, 'Only one of start/end time was provided' + filter_str
assert has_start_date + has_end_date != 1, 'Only one of start/end date was provided' + filter_str
assert (has_SP + has_date != 1) or (has_start_date + has_end_date == 2), 'Only one of date/SP was provided' + filter_str
assert sum(request_type_filter.values()) == 1, 'Request type could not be determined\n\nFilter' + filter_str
return
def determine_request_type_from_fields(
field_names: list,
start_time_cols: list=['StartTime'],
end_time_cols: list=['EndTime'],
start_date_cols: list=['StartDate', 'FromSettlementDate', 'FromDate'],
end_date_cols: list=['EndDate', 'ToSettlementDate', 'ToDate'],
date_cols: list=['SettlementDate', 'ImplementationDate', 'DecommissioningDate', 'Date', 'startTimeOfHalfHrPeriod'],
SP_cols: list=['SettlementPeriod', 'Period'],
year_cols: list=['Year'],
month_cols: list=['Month', 'MonthName'],
week_cols: list=['Week']
):
has_start_time = bool(set(field_names).intersection(set(start_time_cols)))
has_end_time = bool(set(field_names).intersection(set(end_time_cols)))
has_start_date = bool(set(field_names).intersection(set(start_date_cols)))
has_end_date = bool(set(field_names).intersection(set(end_date_cols)))
has_date = bool(set(field_names).intersection(set(date_cols)))
has_SP = bool(set(field_names).intersection(set(SP_cols)))
has_year = bool(set(field_names).intersection(set(year_cols)))
has_month = bool(set(field_names).intersection(set(month_cols)))
has_week = bool(set(field_names).intersection(set(week_cols)))
request_type_filter = construct_request_type_filter(
has_start_time, has_end_time, has_start_date,
has_end_date, has_date, has_SP, has_year, has_month, has_week
)
check_request_type_filter(
field_names, request_type_filter, has_start_time, has_end_time, has_start_date,
has_end_date, has_date, has_SP, has_year, has_month, has_week
)
request_type = [k for k, v in request_type_filter.items() if v==True][0]
return request_type
method = 'get_B1610'
field_names = list(method_to_params[method].keys())
request_type = determine_request_type_from_fields(field_names)
request_type
'SP_and_date'
#exports
def determine_method_request_types(method_to_params):
method_to_request_type = dict()
for method in method_to_params.keys():
field_names = list(method_to_params[method].keys())
method_to_request_type[method] = determine_request_type_from_fields(field_names)
return method_to_request_type
method_to_request_type = determine_method_request_types(method_to_params)
pd.Series(method_to_request_type).value_counts()
SP_and_date 22
date_time_range 9
non_temporal 8
year 5
year_and_month 3
year_and_week 1
date_range 1
dtype: int64
#exports
def construct_method_to_params_map(method_to_params):
standardised_params_map = {
'start_time': ['StartTime'],
'end_time': ['EndTime'],
'start_date': ['StartDate', 'FromSettlementDate', 'FromDate'],
'end_date': ['EndDate', 'ToSettlementDate', 'ToDate'],
'date': ['SettlementDate', 'ImplementationDate', 'DecommissioningDate', 'Date', 'startTimeOfHalfHrPeriod'],
'SP': ['SettlementPeriod', 'Period'],
'year': ['Year'],
'month': ['Month', 'MonthName'],
'week': ['Week']
}
method_to_params_map = dict()
for method, params in method_to_params.items():
method_to_params_map[method] = dict()
for param in params.keys():
for standardised_param, bmrs_params in standardised_params_map.items():
if param in bmrs_params:
method_to_params_map[method][standardised_param] = param
return method_to_params_map
method_to_params_map = construct_method_to_params_map(method_to_params)
pd.Series(method_to_params_map).head(3).to_dict()
{'get_B0610': {'date': 'SettlementDate', 'SP': 'Period'},
'get_B0620': {'date': 'SettlementDate', 'SP': 'Period'},
'get_B0630': {'year': 'Year', 'week': 'Week'}}
#exports
def construct_method_info_dict(API_yaml_fp: str):
API_yaml = specgen.load_API_yaml(API_yaml_fp)
method_to_params = construct_method_to_params_dict(API_yaml)
method_to_request_type = determine_method_request_types(method_to_params)
method_to_params_map = construct_method_to_params_map(method_to_params)
method_info = dict()
for method, params in method_to_params.items():
method_info[method] = dict()
method_info[method]['request_type'] = method_to_request_type[method]
method_info[method]['kwargs_map'] = method_to_params_map[method]
method_info[method]['func_kwargs'] = {
(
{v: k for k, v in method_to_params_map[method].items()}[k]
if k in method_to_params_map[method].values()
else k
): v
for k, v
in method_to_params[method].items()
}
return method_info
method_info = construct_method_info_dict('../data/BMRS_API.yaml')
JSON([method_info])
<IPython.core.display.JSON object>