Add new Remote Python Debugger integration (#36960)

This commit is contained in:
Franck Nijhof 2020-06-22 15:17:59 +02:00 committed by GitHub
parent 9d40ae96b5
commit b47be05efc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 160 additions and 1 deletions

View File

@ -86,6 +86,7 @@ homeassistant/components/cpuspeed/* @fabaff
homeassistant/components/cups/* @fabaff
homeassistant/components/daikin/* @fredrike
homeassistant/components/darksky/* @fabaff
homeassistant/components/debugpy/* @frenck
homeassistant/components/deconz/* @Kane610
homeassistant/components/delijn/* @bollewolle @Emilv2
homeassistant/components/demo/* @home-assistant/core

View File

@ -40,7 +40,7 @@ DATA_LOGGING = "logging"
LOG_SLOW_STARTUP_INTERVAL = 60
DEBUGGER_INTEGRATIONS = {"ptvsd"}
DEBUGGER_INTEGRATIONS = {"debugpy", "ptvsd"}
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
LOGGING_INTEGRATIONS = {
# Set log levels

View File

@ -0,0 +1,79 @@
"""The Remote Python Debugger integration."""
from asyncio import Event
import logging
from threading import Thread
from typing import Optional
import debugpy
import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType
DOMAIN = "debugpy"
CONF_WAIT = "wait"
CONF_START = "start"
SERVICE_START = "start"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(CONF_HOST, default="0.0.0.0"): cv.string,
vol.Optional(CONF_PORT, default=5678): cv.port,
vol.Optional(CONF_START, default=True): cv.boolean,
vol.Optional(CONF_WAIT, default=False): cv.boolean,
}
)
},
extra=vol.ALLOW_EXTRA,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Remote Python Debugger component."""
conf = config[DOMAIN]
async def debug_start(
call: Optional[ServiceCall] = None, *, wait: bool = True
) -> None:
"""Start the debugger."""
debugpy.listen((conf[CONF_HOST], conf[CONF_PORT]))
wait = conf[CONF_WAIT]
if wait:
_LOGGER.warning(
"Waiting for remote debug connection on %s:%s",
conf[CONF_HOST],
conf[CONF_PORT],
)
ready = Event()
def waitfor():
debugpy.wait_for_client()
hass.loop.call_soon_threadsafe(ready.set)
Thread(target=waitfor).start()
await ready.wait()
else:
_LOGGER.warning(
"Listening for remote debug connection on %s:%s",
conf[CONF_HOST],
conf[CONF_PORT],
)
async_register_admin_service(
hass, DOMAIN, SERVICE_START, debug_start, schema=vol.Schema({})
)
# If set to start the debugger on startup, do so
if conf[CONF_START]:
await debug_start(wait=conf[CONF_WAIT])
return True

View File

@ -0,0 +1,8 @@
{
"domain": "debugpy",
"name": "Remote Python Debugger",
"documentation": "https://www.home-assistant.io/integrations/debugpy",
"requirements": ["debugpy==1.0.0b11"],
"codeowners": ["@frenck"],
"quality_scale": "internal"
}

View File

@ -0,0 +1,3 @@
# Describes the format for available Remote Python Debugger services
start:
description: Start the Remote Python Debugger.

View File

@ -456,6 +456,9 @@ datadog==0.15.0
# homeassistant.components.metoffice
datapoint==0.9.5
# homeassistant.components.debugpy
debugpy==1.0.0b11
# homeassistant.components.decora
# decora==0.6

View File

@ -211,6 +211,9 @@ datadog==0.15.0
# homeassistant.components.metoffice
datapoint==0.9.5
# homeassistant.components.debugpy
debugpy==1.0.0b11
# homeassistant.components.ihc
# homeassistant.components.namecheapdns
# homeassistant.components.ohmconnect

View File

@ -0,0 +1 @@
"""Tests for the Remote Python Debugger integration."""

View File

@ -0,0 +1,61 @@
"""Tests for the Remote Python Debugger integration."""
import pytest
from homeassistant.components.debugpy import (
CONF_HOST,
CONF_PORT,
CONF_START,
CONF_WAIT,
DOMAIN,
SERVICE_START,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.async_mock import patch
@pytest.fixture
def mock_debugpy():
"""Mock debugpy lib."""
with patch("homeassistant.components.debugpy.debugpy") as mocked_debugpy:
yield mocked_debugpy
async def test_default(hass: HomeAssistant, mock_debugpy) -> None:
"""Test if the default settings work."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
mock_debugpy.listen.assert_called_once_with(("0.0.0.0", 5678))
mock_debugpy.wait_for_client.assert_not_called()
assert len(mock_debugpy.method_calls) == 1
async def test_wait_on_startup(hass: HomeAssistant, mock_debugpy) -> None:
"""Test if the waiting for client is called."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_WAIT: True}})
mock_debugpy.listen.assert_called_once_with(("0.0.0.0", 5678))
mock_debugpy.wait_for_client.assert_called_once()
assert len(mock_debugpy.method_calls) == 2
async def test_on_demand(hass: HomeAssistant, mock_debugpy) -> None:
"""Test on-demand debugging using a service call."""
assert await async_setup_component(
hass,
DOMAIN,
{DOMAIN: {CONF_START: False, CONF_HOST: "127.0.0.1", CONF_PORT: 80}},
)
mock_debugpy.listen.assert_not_called()
mock_debugpy.wait_for_client.assert_not_called()
assert len(mock_debugpy.method_calls) == 0
await hass.services.async_call(
DOMAIN, SERVICE_START, blocking=True,
)
mock_debugpy.listen.assert_called_once_with(("127.0.0.1", 80))
mock_debugpy.wait_for_client.assert_not_called()
assert len(mock_debugpy.method_calls) == 1