Source code for sktime_mcp.data.base
"""
Base adapter for data sources.
Defines the interface that all data source adapters must implement.
"""
from abc import ABC, abstractmethod
from typing import Any
import pandas as pd
[docs]
class DataSourceAdapter(ABC):
"""
Abstract base class for all data source adapters.
All adapters must implement:
- load(): Fetch data from source
- validate(): Check data quality
- to_sktime_format(): Convert to sktime-compatible format
"""
[docs]
def __init__(self, config: dict[str, Any]):
"""
Initialize the adapter.
Args:
config: Configuration dictionary specific to the adapter type
"""
self.config = config
self._data = None
self._metadata = {}
[docs]
@abstractmethod
def load(self) -> pd.DataFrame:
"""
Load data from the source (synchronous).
Returns:
DataFrame with time index
"""
pass
[docs]
async def load_async(self, job_id: str | None = None) -> pd.DataFrame:
"""
Load data from the source (asynchronous).
Default implementation runs the synchronous load() in a separate thread.
Adapters should override this for true non-blocking async IO.
Args:
job_id: Optional job ID for progress reporting
Returns:
DataFrame with time index
"""
import asyncio
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self.load)
[docs]
@abstractmethod
def validate(self, data: pd.DataFrame) -> tuple[bool, dict[str, Any]]:
"""
Validate data quality.
Args:
data: DataFrame to validate
Returns:
Tuple of ``(is_valid, validation_report)``, where
``validation_report`` contains the keys ``valid`` (bool),
``errors`` (list of str), and ``warnings`` (list of str).
"""
pass
[docs]
def to_sktime_format(self, data: pd.DataFrame) -> tuple[pd.Series, pd.DataFrame | None]:
"""
Convert to sktime format (y, X).
Args:
data: DataFrame to convert
Returns:
Tuple of (y, X) where:
- y: Target time series (pd.Series with DatetimeIndex)
- X: Exogenous variables (pd.DataFrame, optional)
"""
# Get target column from config
target_col = self.config.get("target_column")
exog_cols = self.config.get("exog_columns", [])
if target_col is not None and target_col not in data.columns:
available_columns = ", ".join(repr(col) for col in data.columns)
raise ValueError(
f"Target column {target_col!r} not found in data. "
f"Available columns: [{available_columns}]"
)
if target_col is not None:
y = data[target_col]
# Get exogenous variables if specified
if exog_cols:
valid_exog_cols = [col for col in exog_cols if col in data.columns]
X = data[valid_exog_cols] if valid_exog_cols else None
else:
# Use all columns except target as exogenous
other_cols = [col for col in data.columns if col != target_col]
X = data[other_cols] if other_cols else None
else:
# Default: first column is target, rest are exogenous
if len(data.columns) == 1:
y = data.iloc[:, 0]
X = None
else:
y = data.iloc[:, 0]
X = data.iloc[:, 1:]
# Add a guideline warning if we're defaulting with multiple columns
if not hasattr(self, "_metadata") or self._metadata is None:
self._metadata = {}
if "validation" not in self._metadata:
self._metadata["validation"] = {"valid": True, "errors": [], "warnings": []}
# Ensure it's a dict and has warnings list
val = self._metadata["validation"]
if isinstance(val, dict) and "warnings" in val:
val["warnings"].append(
f"Target column not specified. Defaulting to first column '{data.columns[0]}'. "
"If this is a time index or feature, please specify 'target_column' in config."
)
return y, X
[docs]
def get_metadata(self) -> dict[str, Any]:
"""
Return metadata about the data source.
Returns:
Dictionary with metadata (rows, columns, frequency, etc.)
"""
return self._metadata