Price sources
from kilowahti.sources import PriceSource
from kilowahti.sources.spot_hinta import SpotHintaSource, SpotHintaRateLimitError
PriceSource (ABC)
Abstract base class for all price data sources. Implement this to add a new data backend.
class PriceSource(ABC):
async def fetch_today(
self,
session: aiohttp.ClientSession,
region: str,
resolution: PriceResolution,
) -> list[PriceSlot]: ...
async def fetch_tomorrow(
self,
session: aiohttp.ClientSession,
region: str,
resolution: PriceResolution,
) -> list[PriceSlot] | None: ...
Contract
| Method | Returns | Raises |
|---|---|---|
fetch_today |
Slots sorted by dt_utc ascending |
aiohttp.ClientResponseError on HTTP error |
fetch_tomorrow |
Slots or None if not yet published |
aiohttp.ClientResponseError on HTTP error (except 404) |
The session parameter is an aiohttp.ClientSession managed by the caller. Sources are stateless; a new session can be passed on each call.
SpotHintaSource
Fetches prices from the spot-hinta.fi REST API (no API key required).
source = SpotHintaSource()
async with aiohttp.ClientSession() as session:
today = await source.fetch_today(session, region="FI", resolution=PriceResolution.HOUR)
tomorrow = await source.fetch_tomorrow(session, region="FI", resolution=PriceResolution.HOUR)
# tomorrow is None if prices haven't been published yet (typically before ~13:00 local)
API details
- Base URL:
https://api.spot-hinta.fi - Today:
GET /Today?region=FI&priceResolution=60 - Tomorrow:
GET /DayForward?region=FI&priceResolution=60 - Rate limit: 1 request per minute per IP
- Prices are returned in €/kWh;
SpotHintaSourceconverts to c/kWh internally
Error handling
| HTTP status | Behaviour |
|---|---|
| 404 | fetch_tomorrow returns None; fetch_today raises |
| 429 | Raises SpotHintaRateLimitError with retry_after (seconds) |
| Other 4xx / 5xx | Raises aiohttp.ClientResponseError |
SpotHintaRateLimitError
class SpotHintaRateLimitError(aiohttp.ClientResponseError):
retry_after: int # seconds to wait before retrying
Raised when the API returns HTTP 429. The retry_after value is taken from the Retry-After response header (defaults to 60 seconds if the header is absent).
try:
slots = await source.fetch_today(session, region="FI", resolution=PriceResolution.HOUR)
except SpotHintaRateLimitError as e:
print(f"Rate limited, retry in {e.retry_after}s")
Implementing a custom source
from kilowahti.sources import PriceSource
from kilowahti.models import PriceResolution, PriceSlot
import aiohttp
class MyCustomSource(PriceSource):
async def fetch_today(
self,
session: aiohttp.ClientSession,
region: str,
resolution: PriceResolution,
) -> list[PriceSlot]:
# fetch from your API and return sorted PriceSlot list
...
async def fetch_tomorrow(
self,
session: aiohttp.ClientSession,
region: str,
resolution: PriceResolution,
) -> list[PriceSlot] | None:
# return None if tomorrow's prices aren't available yet
...