'Platform not found' for simple custom component in 0.94.2

Starting with HA 0.94.0, the “Platform not found” error should show the exact exception that caused it. See these lines. With __init__.py, and with the manifest.json as you think it should be, what are you seeing now in the “Platform not found” error?

Don’t cd to /config/custom_components/whatever, cd to /config, and try it again.

EDIT: Also, it’s important to be in the exact same Python context that HA will run in. Is that a venv? If you’re not in that environment, then any imports in the custom component will probably fail.

manifest.json contents:

{
  "domain": "whatever",
  "name": "Whatever",
  "documentation": "https://www.home-assistant.io/components/mqtt",
  "requirements": [],
  "dependencies": ["mqtt"],
  "codeowners": []
}

__init__.py contents:

"""This is my whatever integration."""

Screenshot of error message after a restart:
Screenshot%20from%202019-06-13%2015-18-49

Screenshot of the container console after carrying out your suggestion:
Screenshot%20from%202019-06-13%2015-16-57

Nothing reported in the console so not sure if that’s good or bad or a bit of both.

FWIW, I restarted the container (yet again) and examined the log more closely. In fact, it does claim to have loaded the whatever component. I just failed to see it previously because it appears earlier in the log:

2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up timer
2019-06-13 15:30:10 INFO (SyncWorker_9) [homeassistant.loader] Loaded whatever from custom_components.whatever
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up input_boolean
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up python_script
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up sun
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setup of domain sun took 0.0 seconds.
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up alarm_control_panel
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up lovelace
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setup of domain lovelace took 0.0 seconds.
2019-06-13 15:30:10 INFO (MainThread) [homeassistant.setup] Setting up discovery
2019-06-13 15:30:11 INFO (MainThread) [homeassistant.setup] Setup of domain discovery took 0.0 seconds.
2019-06-13 15:30:11 INFO (MainThread) [homeassistant.setup] Setting up group
2019-06-13 15:30:11 INFO (MainThread) [homeassistant.setup] Setting up input_select
2019-06-13 15:30:11 WARNING (MainThread) [homeassistant.loader] You are using a custom integration for whatever which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.
2019-06-13 15:30:11 INFO (SyncWorker_7) [homeassistant.loader] Loaded syslog from homeassistant.components.syslog

I assume the fact it loaded the component jibes with the results of carrying out your test where it raised no fuss with import custom_components.whatever.

Nevertheless, it still fails to create the defined climate entity using the whatever platform.

Suffering cats! Makes me want to fall back to using my component, based on 0.89, with 0.94.2 and just ignore the logged errors about ‘deprecation’. I realize I’m just delaying the inevitable and in some future version (0.95? 0.96?) it’ll probably escalate from ‘deprecated’ to ‘unsupported’.

I can’t put my finger on it yet but some silly little thing is making this needlessly difficult …

In case anyone is still following along, here’s another thing that failed to resolve this issue.

Before the advent of manifest.json, dependencies were specified within the component’s code. So I added it to the climate component’s code (i.e. the one using the 0.94.2 version of MQTT HVAC). So now it’s specified in two places, in the code and in manifest.json.

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['mqtt']

DEFAULT_NAME = 'MQTT HVAC'

It didn’t help. Home Assistant continues to report:

Platform not found: climate.whatever

I then edited manifest.json and eliminated the dependency on mqtt.

{
  "domain": "whatever",
  "name": "Whatever",
  "documentation": "https://www.home-assistant.io/components/mqtt",
  "requirements": [],
  "dependencies": [],
  "codeowners": []
}

Now the reference to a dependency is only in one place, in the code. That didn’t fix it either.

So far this has been in an exercise in documenting everything that does not work to correct this problem …

I re-read this section in the Great Migration blog: Note for custom component developers, specifically the third bullet point:

If you want to share an adjusted version of a Home Assistant integration, copy over ALL the files. Do your users a favor and stick to relative imports to avoid having your component break during an upgrade. Example of a relative import is from . import DATA_BRIDGE .

Relative imports? Uh-oh!

If you copy a stock component into custom_components, you’ve changed its location. If you don’t copy any other part of the integration it belongs to, then it now stands alone. If it uses relative imports, it will look for them relative to its new location in custom_components … and won’t find them.

I checked the code in climate.py version 0.94.2 and, sure enough, it uses two relative imports:

from . import (
    ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_UNIQUE_ID,
    MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes, MqttAvailability,
    MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
from .discovery import MQTT_DISCOVERY_NEW, clear_discovery_hash

I checked the code in my functional climate.py based on code from version 0.89 and those two imports use absolute references, not relative:

from homeassistant.components.mqtt import (
    ATTR_DISCOVERY_HASH, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
    CONF_UNIQUE_ID, MQTT_BASE_PLATFORM_SCHEMA, MqttAttributes,
    MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import (
    MQTT_DISCOVERY_NEW, clear_discovery_hash)

I commented out the relative imports, pasted in the absolute ones, restarted Home Assistant and, ta-dah, it works now. It found the whatever platform and created the climate.dummy entity.

It all seems so obvious now but so many other possibilities had to be eliminated to get to this point!

3 Likes

Really enjoyed this post, you describe how lots of things work with good example. It has been entertaining following along and see what is required to update, pretty sure this will help more people who upgrade and/or use custom_components! :smiley:

Yeah, I was wondering about that. Specifically, that you chose a piece of a large integration and not the whole thing and wondered if that might be the problem – climate.py was importing stuff from the other pieces. But I thought, if that were the case, you’d see that when you did the import test. I guess the test was flawed. Maybe if I had suggested:

from custom_components.whatever import climate

or even:

import custom_components.whatever.climate

it would have provided more useful feedback. D’oh! Sorry about that. I guess I wasn’t thinking. The thought was if you imported the “package”, then it would import everything in the package. But really, if __init__.py didn’t import anything from climate.py, that wouldn’t be the case.

To be precise, this problem may occur in a very specific situation and not for all custom_components. I’m taking just one component (climate.py) of a large integration (mqtt) and identifying it as a new platform (in this example I called it whatever).

The code inside the climate.py file contains references to other parts of the mqtt integration. Two of those references were relative to the file’s location. It’s those two references that caused the problem because my copy of climate.py is located here:

custom_components/whatever

and not in its original location here:

components/mqtt

So this problem may occur only for people creating custom_components for portions of existing, large integrations (and those portions contain relative imports).


@pnbruckner

I reverted the code to use relative imports in order to test your suggestion to use:

import custom_components.whatever.climate

In this case, the python3 interpreter did report an error, but not one that points to the issue of relative imports. It carps that it can’t find the homeassistant module.

FWIW, the other command (from custom_components.whatever import climate) prodcued the same error message.

That’s probably because you’re not in the same Python context as HA normally runs in. What type of install are you using? If it’s a venv, then first activate that venv, then run Python and the import command.

It’s a docker container. I’m using Portainer’s ‘Container Console’ to access the container’s command line (defaults to user root). All files related to Home Assistant are owned by root.

Well, you’ve already figured out your issue, but for the future, you might want to figure out how to get into HA’s Python context. I don’t use Docker, so I have no idea how that works (i.e., how HA is launched in that context.)

That makes two of us. The container is a black box and I know very little about how Home Assistant is configured within it. There’s this reference but the only thing meaningful to me is the last line which suggests a very vanilla startup, running as the default user (root):


CMD [ "python", "-m", "homeassistant", "--config", "/config" ]

We might be beating a dead horse here, but try these and see what you get:

python -m site
python3 -m site

Results:

root@plutonium:/usr/src/app# cd /config
root@plutonium:/config# python -m site
sys.path = [
‘/config’,
‘/usr/local/lib/python37.zip’,
‘/usr/local/lib/python3.7’,
‘/usr/local/lib/python3.7/lib-dynload’,
‘/usr/local/lib/python3.7/site-packages’,
]
USER_BASE: ‘/root/.local’ (doesn’t exist)
USER_SITE: ‘/root/.local/lib/python3.7/site-packages’ (doesn’t exist)
ENABLE_USER_SITE: True
root@plutonium:/config# python3 -m site
sys.path = [
‘/config’,
‘/usr/local/lib/python37.zip’,
‘/usr/local/lib/python3.7’,
‘/usr/local/lib/python3.7/lib-dynload’,
‘/usr/local/lib/python3.7/site-packages’,
]
USER_BASE: ‘/root/.local’ (doesn’t exist)
USER_SITE: ‘/root/.local/lib/python3.7/site-packages’ (doesn’t exist)
ENABLE_USER_SITE: True
root@plutonium:/config#

The lines with (doesn't exist) are verbatim from the console and not me adding commentary.

Ok, I think this might work:

cd /usr/src/app
python
import sys
sys.path.insert(0, '/config/custom_components')
import whatever.climate

EDIT: Actually, to be closer to how HA does it:

cd /usr/src/app
python
import sys
sys.path.insert(0, '/config')
import custom_components.whatever.climate
1 Like

OK, that sequence of commands caused the relative import to reveal itself as a problem:

In my climate.py, line 26 is the first use of the relative import:
Screenshot%20from%202019-06-14%2012-48-15

Before we finally bury Old Nelly, can you explain those python commands?

See Intra-package References.

Basically dot means the current package. So in this case, for the original climate.py, in its original location, . means homeassistant.components.mqtt (or homeassistant/components/mqtt/__init__.py.) And .discovery means homeassistant.components.mqtt.discovery (or homeassistant/components/mqtt/discovery.py.)

What I’ve done in the past, rather than change the import statements, or copying all the files, I simply created indirect references. E.g., in this case I would create /config/custom_components/whatever/__init__.py which would contain:

from homeassistant.components.mqtt import *

and /config/custom_components/whatever/const.py that would contain:

from homeassistant.components.mqtt.const import *

etc.

Or, although I haven’t tried this, you might be able to just make symlinks:

cd /config/custom_components/whatever
ln -s /usr/src/app/homeassistant/components/mqtt/__init__.py __init__.py
...

Either of these last two options allows you to copy and modify only what you want, while effectively including the other files as-is. When you update HA you automatically get any new versions – which can be a good thing, or a bad thing. :wink:

Thanks for the link. What I should’ve said was what is the purpose of these commands:
import sys
sys.path.insert(0, '/config')

I found this link explaining sys and my guess is the two commands are being used to configure python’s environment?

I like the suggestions you’ve made for incorporating the from statements into __init__.py.

I finished creating a customized version of 0.94’s MQTT climate.py. It took longer than expected because the code changed since 0.89 (for the better, but it created more work for me). The few tests I’ve done confirm it’s working as desired. FWIW, I have no __init__.py in the directory (just climate.py and manifest.json) and Home Assistant seems to have no problem with that arrangement.

I’m hoping this is the last time I will need to create a customized version of MQTT’s climate component. pvizeli is redesigning the entire climate component (for all platforms). I know at least one of the things it will do (that one of my modifications does now) is to make a distinction between an HVAC system’s operating mode (auto, heat, cool, off) and its operating state (heating, cooling, drying, idle).

Oh, sorry, I misunderstood what you were asking.

Yeah, basically the import mechanism will use sys.path to search for modules & packages. You’ll notice that when I had you run that site package it output what sys.path would be (although I had you do it originally from the wrong directory. Should have been in /usr/src/apps/homeassisant, which I figured out later.) You can add paths to sys.path (which is just a Python list of path strings) to make it look in additional places. In fact, that’s what HA’s loader does:

I just suggested you do the same thing “manually.” :slight_smile:

1 Like