Add SSDP integration (#24090)
* Add SSDP integration * Fix tests * Sort all the things * Add netdisco to test requirements
This commit is contained in:
@@ -89,6 +89,7 @@ TEST_REQUIREMENTS = (
|
||||
'luftdaten',
|
||||
'mbddns',
|
||||
'mficlient',
|
||||
'netdisco',
|
||||
'numpy',
|
||||
'oauth2client',
|
||||
'paho-mqtt',
|
||||
|
||||
@@ -4,15 +4,23 @@ import sys
|
||||
|
||||
from .model import Integration, Config
|
||||
from . import (
|
||||
dependencies, manifest, codeowners, services, config_flow, zeroconf)
|
||||
codeowners,
|
||||
config_flow,
|
||||
dependencies,
|
||||
manifest,
|
||||
services,
|
||||
ssdp,
|
||||
zeroconf,
|
||||
)
|
||||
|
||||
PLUGINS = [
|
||||
manifest,
|
||||
dependencies,
|
||||
codeowners,
|
||||
services,
|
||||
config_flow,
|
||||
zeroconf
|
||||
dependencies,
|
||||
manifest,
|
||||
services,
|
||||
ssdp,
|
||||
zeroconf,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ MANIFEST_SCHEMA = vol.Schema({
|
||||
vol.Required('name'): str,
|
||||
vol.Optional('config_flow'): bool,
|
||||
vol.Optional('zeroconf'): [str],
|
||||
vol.Optional('ssdp'): vol.Schema({
|
||||
vol.Optional('st'): [str],
|
||||
vol.Optional('manufacturer'): [str],
|
||||
vol.Optional('device_type'): [str],
|
||||
}),
|
||||
vol.Required('documentation'): str,
|
||||
vol.Required('requirements'): [str],
|
||||
vol.Required('dependencies'): [str],
|
||||
|
||||
88
script/hassfest/ssdp.py
Normal file
88
script/hassfest/ssdp.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""Generate ssdp file."""
|
||||
from collections import OrderedDict, defaultdict
|
||||
import json
|
||||
from typing import Dict
|
||||
|
||||
from .model import Integration, Config
|
||||
|
||||
BASE = """
|
||||
\"\"\"Automatically generated by hassfest.
|
||||
|
||||
To update, run python3 -m hassfest
|
||||
\"\"\"
|
||||
|
||||
|
||||
SSDP = {}
|
||||
""".strip()
|
||||
|
||||
|
||||
def sort_dict(value):
|
||||
"""Sort a dictionary."""
|
||||
return OrderedDict((key, value[key])
|
||||
for key in sorted(value))
|
||||
|
||||
|
||||
def generate_and_validate(integrations: Dict[str, Integration]):
|
||||
"""Validate and generate ssdp data."""
|
||||
data = {
|
||||
'st': defaultdict(list),
|
||||
'manufacturer': defaultdict(list),
|
||||
'device_type': defaultdict(list),
|
||||
}
|
||||
|
||||
for domain in sorted(integrations):
|
||||
integration = integrations[domain]
|
||||
|
||||
if not integration.manifest:
|
||||
continue
|
||||
|
||||
ssdp = integration.manifest.get('ssdp')
|
||||
|
||||
if not ssdp:
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(str(integration.path / "config_flow.py")) as fp:
|
||||
if ' async_step_ssdp(' not in fp.read():
|
||||
integration.add_error(
|
||||
'ssdp', 'Config flow has no async_step_ssdp')
|
||||
continue
|
||||
except FileNotFoundError:
|
||||
integration.add_error(
|
||||
'ssdp',
|
||||
'SSDP info in a manifest requires a config flow to exist'
|
||||
)
|
||||
continue
|
||||
|
||||
for key in 'st', 'manufacturer', 'device_type':
|
||||
if key not in ssdp:
|
||||
continue
|
||||
|
||||
for value in ssdp[key]:
|
||||
data[key][value].append(domain)
|
||||
|
||||
data = sort_dict({key: sort_dict(value) for key, value in data.items()})
|
||||
return BASE.format(json.dumps(data, indent=4))
|
||||
|
||||
|
||||
def validate(integrations: Dict[str, Integration], config: Config):
|
||||
"""Validate ssdp file."""
|
||||
ssdp_path = config.root / 'homeassistant/generated/ssdp.py'
|
||||
config.cache['ssdp'] = content = generate_and_validate(integrations)
|
||||
|
||||
with open(str(ssdp_path), 'r') as fp:
|
||||
if fp.read().strip() != content:
|
||||
config.add_error(
|
||||
"ssdp",
|
||||
"File ssdp.py is not up to date. "
|
||||
"Run python3 -m script.hassfest",
|
||||
fixable=True
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
def generate(integrations: Dict[str, Integration], config: Config):
|
||||
"""Generate ssdp file."""
|
||||
ssdp_path = config.root / 'homeassistant/generated/ssdp.py'
|
||||
with open(str(ssdp_path), 'w') as fp:
|
||||
fp.write(config.cache['ssdp'] + '\n')
|
||||
@@ -31,6 +31,19 @@ def generate_and_validate(integrations: Dict[str, Integration]):
|
||||
if not service_types:
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(str(integration.path / "config_flow.py")) as fp:
|
||||
if ' async_step_zeroconf(' not in fp.read():
|
||||
integration.add_error(
|
||||
'zeroconf', 'Config flow has no async_step_zeroconf')
|
||||
continue
|
||||
except FileNotFoundError:
|
||||
integration.add_error(
|
||||
'zeroconf',
|
||||
'Zeroconf info in a manifest requires a config flow to exist'
|
||||
)
|
||||
continue
|
||||
|
||||
for service_type in service_types:
|
||||
|
||||
if service_type not in service_type_dict:
|
||||
|
||||
Reference in New Issue
Block a user