Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PLATFORM_SCHEMA_BASE and use it in alarm_control_panel #20224

Merged
merged 8 commits into from Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion homeassistant/components/alarm_control_panel/__init__.py
Expand Up @@ -13,7 +13,8 @@
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.config_validation import ( # noqa
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
Expand Down
12 changes: 9 additions & 3 deletions homeassistant/config.py
Expand Up @@ -743,13 +743,19 @@ def async_process_component_config(
async_log_exception(ex, domain, config, hass)
return None

elif hasattr(component, 'PLATFORM_SCHEMA'):
elif (hasattr(component, 'PLATFORM_SCHEMA') or
hasattr(component, 'PLATFORM_SCHEMA_BASE')):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA( # type: ignore
p_config)
if hasattr(component, 'PLATFORM_SCHEMA_BASE'):
p_validated = \
component.PLATFORM_SCHEMA_BASE( # type: ignore
p_config)
else:
p_validated = component.PLATFORM_SCHEMA( # type: ignore
p_config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
continue
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/helpers/config_validation.py
Expand Up @@ -504,6 +504,15 @@ def validator(value):
vol.Optional(CONF_SCAN_INTERVAL): time_period
}, extra=vol.ALLOW_EXTRA)

# This will replace PLATFORM_SCHEMA once all base components are updated
PLATFORM_SCHEMA_2 = vol.Schema({
vol.Required(CONF_PLATFORM): string,
vol.Optional(CONF_SCAN_INTERVAL): time_period
})

PLATFORM_SCHEMA_BASE = PLATFORM_SCHEMA_2.extend({
}, extra=vol.ALLOW_EXTRA)

EVENT_SCHEMA = vol.Schema({
vol.Optional(CONF_ALIAS): string,
vol.Required('event'): string,
Expand Down
7 changes: 5 additions & 2 deletions tests/common.py
Expand Up @@ -435,8 +435,8 @@ class MockModule:
# pylint: disable=invalid-name
def __init__(self, domain=None, dependencies=None, setup=None,
requirements=None, config_schema=None, platform_schema=None,
async_setup=None, async_setup_entry=None,
async_unload_entry=None):
platform_schema_base=None, async_setup=None,
async_setup_entry=None, async_unload_entry=None):
"""Initialize the mock module."""
self.DOMAIN = domain
self.DEPENDENCIES = dependencies or []
Expand All @@ -448,6 +448,9 @@ def __init__(self, domain=None, dependencies=None, setup=None,
if platform_schema is not None:
self.PLATFORM_SCHEMA = platform_schema

if platform_schema_base is not None:
self.PLATFORM_SCHEMA_BASE = platform_schema_base

if setup is not None:
# We run this in executor, wrap it in function
self.setup = lambda *args: setup(*args)
Expand Down
118 changes: 115 additions & 3 deletions tests/test_setup.py
Expand Up @@ -14,7 +14,8 @@
import homeassistant.config as config_util
from homeassistant import setup, loader
import homeassistant.util.dt as dt_util
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE)
from homeassistant.helpers import discovery

from tests.common import \
Expand Down Expand Up @@ -94,18 +95,24 @@ def test_validate_platform_config(self):
platform_schema = PLATFORM_SCHEMA.extend({
'hello': str,
})
platform_schema_base = PLATFORM_SCHEMA_BASE.extend({
})
loader.set_component(
self.hass,
'platform_conf',
MockModule('platform_conf', platform_schema=platform_schema))
MockModule('platform_conf',
platform_schema_base=platform_schema_base))

loader.set_component(
self.hass,
'platform_conf.whatever', MockPlatform('whatever'))
'platform_conf.whatever',
MockPlatform('whatever',
platform_schema=platform_schema))

with assert_setup_component(0):
assert setup.setup_component(self.hass, 'platform_conf', {
'platform_conf': {
'platform': 'whatever',
'hello': 'world',
'invalid': 'extra',
}
Expand All @@ -121,6 +128,7 @@ def test_validate_platform_config(self):
'hello': 'world',
},
'platform_conf 2': {
'platform': 'whatever',
'invalid': True
}
})
Expand Down Expand Up @@ -175,6 +183,110 @@ def test_validate_platform_config(self):
assert 'platform_conf' in self.hass.config.components
assert not config['platform_conf'] # empty

def test_validate_platform_config_2(self):
"""Test component PLATFORM_SCHEMA_BASE prio over PLATFORM_SCHEMA."""
platform_schema = PLATFORM_SCHEMA.extend({
'cheers': str,
'hello': str,
})
platform_schema_base = PLATFORM_SCHEMA.extend({
'hello': str,
emontnemery marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eg if we do:

Suggested change
'hello': str,
'hello': 'world',

We can below check if platforms validate this item correctly, instead of adding other keys, since the schema should allow extra.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, fixed now!

})
loader.set_component(
self.hass,
'platform_conf',
MockModule('platform_conf',
platform_schema=platform_schema,
platform_schema_base=platform_schema_base))

loader.set_component(
self.hass,
'platform_conf.whatever',
MockPlatform('whatever',
platform_schema=platform_schema))

with assert_setup_component(0):
assert setup.setup_component(self.hass, 'platform_conf', {
# Should fail: no extra keys allowed
'platform_conf': {
'platform': 'whatever',
'hello': 'world',
'invalid': 'extra',
}
})

self.hass.data.pop(setup.DATA_SETUP)
self.hass.config.components.remove('platform_conf')

with assert_setup_component(1):
assert setup.setup_component(self.hass, 'platform_conf', {
# Should pass
'platform_conf': {
'platform': 'whatever',
'hello': 'world',
},
# Should fail: key cheers not in component platform_schema_base
'platform_conf 2': {
'platform': 'whatever',
'hello': 'world',
'cheers': 'mate'
}
})

self.hass.data.pop(setup.DATA_SETUP)
self.hass.config.components.remove('platform_conf')

def test_validate_platform_config_3(self):
"""Test fallback to component PLATFORM_SCHEMA."""
emontnemery marked this conversation as resolved.
Show resolved Hide resolved
component_schema = PLATFORM_SCHEMA.extend({
'hello': str,
})
platform_schema = PLATFORM_SCHEMA.extend({
'cheers': str,
'hello': str,
})
loader.set_component(
self.hass,
'platform_conf',
MockModule('platform_conf',
platform_schema=component_schema))

loader.set_component(
self.hass,
'platform_conf.whatever',
MockPlatform('whatever',
platform_schema=platform_schema))

with assert_setup_component(0):
assert setup.setup_component(self.hass, 'platform_conf', {
'platform_conf': {
# Should fail: no extra keys allowed
'hello': 'world',
'invalid': 'extra',
}
})

self.hass.data.pop(setup.DATA_SETUP)
self.hass.config.components.remove('platform_conf')

with assert_setup_component(1):
assert setup.setup_component(self.hass, 'platform_conf', {
# Should pass
'platform_conf': {
'platform': 'whatever',
'hello': 'world',
},
# Should fail: key cheers not in component platform_schema
'platform_conf 2': {
'platform': 'whatever',
'hello': 'world',
'cheers': 'mate'
}
})

self.hass.data.pop(setup.DATA_SETUP)
self.hass.config.components.remove('platform_conf')

def test_component_not_found(self):
"""setup_component should not crash if component doesn't exist."""
assert not setup.setup_component(self.hass, 'non_existing')
Expand Down