Spain electricity hourly pricing (PVPC) Core Integration fails in 2026

Spain electricity hourly pricing (PVPC)

Version 0.0.1

The Home Assistant Core Integration Spain electricity hourly pricing (PVPC) - Home Assistant fails to load after 01.01.2026 with KeyError: 2026 - Unexpected error fetching pvpc_hourly_pricing data because the year 2026 isn’t present in the dictionary.

To fix this, the file aiopvpc/pvpc_tariff.py has to be modified. This file belongs to the aiopvpc Python library, which Home Assistant uses for Spanish electricity tariff calculations.

The line:

_NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD[day.year]

must be changed to:

_NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD.get(day.year, set())

There are several ways to do this. My preferred solution is to create a small custom integration that replaces the Core component. It would be possible to patch the Core component (aiopvpc) directly, but it would not survive the next HA core update. By creating a separate custom integration outside the HA container, we can ensure that this patch survives all future HA Core updates until the HA Dev Team makes a permanent fix for it. Pay attention to the change log and as soon as the fix is announced, the custom integration can be deleted.

Before even touching any core component of HA, it’s a good idea to make a backup of the library. Use Terminal, PowerShell, PuTTY or any other SSH client to log into the OS where your HA lives. In my case, since I use HA OS, I first installed the Advanced SSH & Web Terminal Add-on (now called App) and disabled Protection Mode in order to allow full access to the entire system (Do this at your own risk!) During installation and configuration of the Advanced SSH & Web Terminal App, you must set username and password credentials. Then connect using the IP of your HA server and Port 22 (default).

To make a backup issue the following commands:

docker exec homeassistant cp \
/usr/local/lib/python3.13/site-packages/aiopvpc/pvpc_tariff.py \
/usr/local/lib/python3.13/site-packages/aiopvpc/pvpc_tariff.py.bak

Then, in case something goes very wrong, you can restore instantly using this command:

docker exec homeassistant cp \
/usr/local/lib/python3.13/site-packages/aiopvpc/pvpc_tariff.py.bak \
/usr/local/lib/python3.13/site-packages/aiopvpc/pvpc_tariff.py

Then, to create our own custom integration snippet, we copy the built-in integration into the custom_components folder. This is a well-established, safe, and fully supported way to apply runtime patches without touching the container’s Python environment and is exactly how Home Assistant expects advanced users to override core integrations.

Issue the command:

docker cp homeassistant:/usr/src/homeassistant/homeassistant/components/pvpc_hourly_pricing
/config/custom_components/

This copies the entire pvpc_hourly_pricing integration folder into the folder: */config/custom_components/pvpc_hourly_pricing

Then list the folder content using this command:

s -lah /config/custom_components/pvpc_hourly_pricing

This simply verifies that the copy succeeded. It is critical that the folder name is identical to the integration domain, exactly as given above. Do not attempt to use your own folder name.
When, at any time in the future, you wish to cancel the use of the custom component, simply delete the whole folder and its content using this command:

rm -r /config/custom_components/pvpc_hourly_pricing

At the time of writing this guide, the content of my custom integration folder is:


|  drwxr-xr-x | 10 root | root | 4.0K Feb 18 23:00 ..  |
|  -rw-r--r-- | 1 root | root | 1.2K Feb 13 21:09 __init__.py  |
|  drwxr-xr-x | 2 root | root | 4.0K Feb 13 21:11 __pycache__  |
|  -rw-r--r-- | 1 root | root | 8.8K Feb 13 21:09 config_flow.py  |
|  -rw-r--r-- | 1 root | root | 401 Feb 13 21:09 const.py  |
|  -rw-r--r-- | 1 root | root | 2.3K Feb 13 21:09 coordinator.py  |
|  -rw-r--r-- | 1 root | root | 1.4K Feb 13 21:09 helpers.py  |
|  -rw-r--r-- | 1 root | root | 357 Feb 13 21:09 manifest.json  |
|  -rw-r--r-- | 1 root | root | 9.0K Feb 13 21:09 sensor.py  |
|  -rw-r--r-- | 1 root | root | 1.9K Feb 13 21:09 strings.json  |
|  drwxr-xr-x | 2 root | root | 4.0K Feb 13 21:09 translations  |

What we want to do is to modify the file init.py and change the aiopvpc library definitions. If you examine the content of the file init.py you’ll see that it defines:

__NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD = { ... }

And later does:

national_holiday = day in __NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD[day.year]

If day.year is missing, Python raises a KeyError. So we want to change this to:

pvpc_tariff._NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD = defaultdict(set,
pvpc_tariff._NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD)

This wraps the existing dict in a defaultdict(set), so:

  • dict[2026] → returns set()

  • No exception is raised

  • The integration continues normally

  • The tariff calculation logic remains unchanged except for the missing-year behavior

But before making changes to the file, it is always a good idea to make a local backup copy first, using this command:

cp /config/custom_components/pvpc_hourly_pricing/__init__.py \
/config/custom_components/pvpc_hourly_pricing/__init__.py.bak

Then, if you make a mistake during patching, the original can easily be restored using this command:

cp /config/custom_components/pvpc_hourly_pricing/__init__.py.bak \
/config/custom_components/pvpc_hourly_pricing/__init__.py

Now that you have a backup copy, it is safe to delete the original file before creating the one we really need. The alternative is to patch the file directly, but this is more prone to syntax errors. So go ahead and delete the original file using this command:

rm /config/custom_components/pvpc_hourly_pricing/__init__.py

Then create the new, corrected version of this file using this method:

cat > /config/custom_components/pvpc_hourly_pricing/__init__.py << 'EOF'
<your patched file content goes here>
EOF

Here’s the correctly patched full content of the file __init__.py :

"""The pvpc_hourly_pricing integration to collect Spain official electric prices."""

from collections import defaultdict

from homeassistant.const import CONF_API_TOKEN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from .coordinator import ElecPricesDataUpdateCoordinator, PVPCConfigEntry
from .helpers import get_enabled_sensor_keys

PLATFORMS: list[Platform] = [Platform.SENSOR]

async def async_setup_entry(hass: HomeAssistant, entry: PVPCConfigEntry) -> bool:
    """Set up pvpc hourly pricing from a config entry."""

    # Workaround for aiopvpc KeyError on new years (e.g., 2026) when looking up
    # extra holidays by year: _NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD[day.year].
    try:
        import aiopvpc.pvpc_tariff as pvpc_tariff

        pvpc_tariff._NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD = defaultdict(
            set, pvpc_tariff._NATIONAL_EXTRA_HOLIDAYS_FOR_P3_PERIOD
        )
    except (ImportError, AttributeError, TypeError):
        pass

    entity_registry = er.async_get(hass)
    sensor_keys = get_enabled_sensor_keys(
        using_private_api=entry.data.get(CONF_API_TOKEN) is not None,
        entries=er.async_entries_for_config_entry(entity_registry, entry.entry_id),
    )
    coordinator = ElecPricesDataUpdateCoordinator(hass, entry, sensor_keys)
    await coordinator.async_config_entry_first_refresh()

    entry.runtime_data = coordinator
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    return True

async def async_unload_entry(hass: HomeAssistant, entry: PVPCConfigEntry) -> bool:
    """Unload a config entry."""
    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

After creating the file, verify it and check the permissions:

sed -n '1,200p' /config/custom_components/pvpc_hourly_pricing/__init__.py
ls -l /config/custom_components/pvpc_hourly_pricing/__init__.py

Permissions should be -rw-r--r-- and the file size should be 1732 bytes.

Before we invoke this fix, we need to make one more change to ensure that the custom configuration is correctly loaded during restart. The custom copy will be ignored unless the file manifest.json contains a “version” variable which is required for all custom integrations. Without this, Home Assistant falls back to the built-in Core integration.

So first make a backup copy of the existing manifest.json file:

cp /config/custom_components/pvpc_hourly_pricing/manifest.json \
/config/custom_components/pvpc_hourly_pricing/manifest.json.bak

Then delete the existing manifest.json file:

rm /config/custom_components/pvpc_hourly_pricing/manifest.json</your>

Then create the correct manifest.json file:

cat > /config/custom_components/pvpc_hourly_pricing/manifest.json << 'EOF'
{
"domain": "pvpc_hourly_pricing",
"name": "Spain electricity hourly pricing (PVPC)",
"version": "0.0.1",
"codeowners": ["@azogue"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/pvpc_hourly_pricing",
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["aiopvpc"],
"requirements": ["aiopvpc==4.2.2"]
}
EOF

Then, finally restart Home Assistant with this command:

ha core restart

And now all should be good.

If not, then you may want to go into HACS and re-download the PVPC Integration and then redo the whole process while inspecting thoroughly the content of init.py and manifest.json to make sure your original versions do not differ from those I’ve included here.

Have you considered adding an issue to core for this ?
There is a link to the issues in that link you posted.