diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0d63307a020..5299ac0d301 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -534,7 +534,7 @@ async def _async_set_up_integrations( _LOGGER.warning("Setup timed out for stage 1 - moving forward") # Enables after dependencies - async_set_domains_to_be_loaded(hass, stage_1_domains | stage_2_domains) + async_set_domains_to_be_loaded(hass, stage_2_domains) if stage_2_domains: _LOGGER.info("Setting up stage 2: %s", stage_2_domains) diff --git a/homeassistant/components/avea/manifest.json b/homeassistant/components/avea/manifest.json index 8d39600ed46..bf2b1a6a6ec 100644 --- a/homeassistant/components/avea/manifest.json +++ b/homeassistant/components/avea/manifest.json @@ -3,5 +3,5 @@ "name": "Elgato Avea", "documentation": "https://www.home-assistant.io/integrations/avea", "codeowners": ["@pattyland"], - "requirements": ["avea==1.5"] + "requirements": ["avea==1.5.1"] } diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 68579559e35..8b762a82f02 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -640,7 +640,7 @@ class MqttClimate( return self._hold if self._away: return PRESET_AWAY - return None + return PRESET_NONE @property def preset_modes(self): diff --git a/homeassistant/components/onewire/config_flow.py b/homeassistant/components/onewire/config_flow.py index f83431111d8..9ad4d5347f0 100644 --- a/homeassistant/components/onewire/config_flow.py +++ b/homeassistant/components/onewire/config_flow.py @@ -56,7 +56,7 @@ def is_duplicate_owserver_entry(hass: HomeAssistantType, user_input): if ( config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER and config_entry.data[CONF_HOST] == user_input[CONF_HOST] - and config_entry.data[CONF_PORT] == str(user_input[CONF_PORT]) + and config_entry.data[CONF_PORT] == user_input[CONF_PORT] ): return True return False diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 0dfb9ab5098..ffbdfdf386b 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -11,7 +11,7 @@ "zigpy-deconz==0.11.0", "zigpy==0.27.0", "zigpy-xbee==0.13.0", - "zigpy-zigate==0.7.2", + "zigpy-zigate==0.7.3", "zigpy-znp==0.2.2" ], "codeowners": ["@dmulcahey", "@adminiuga"] diff --git a/homeassistant/const.py b/homeassistant/const.py index 927773be73d..37b495d8c24 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 118 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 1) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index c6efa717fa7..fb3e6ba40b5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -49,6 +49,8 @@ _RENDER_INFO = "template.render_info" _ENVIRONMENT = "template.environment" _RE_JINJA_DELIMITERS = re.compile(r"\{%|\{\{|\{#") +# Match "simple" ints and floats. -1.0, 1, +5, 5.0 +_IS_NUMERIC = re.compile(r"^[+-]?(?!0\d)\d*(?:\.\d*)?$") _RESERVED_NAMES = {"contextfunction", "evalcontextfunction", "environmentfunction"} @@ -373,7 +375,19 @@ class Template: # render, by not returning right here. The evaluation of strings # resulting in strings impacts quotes, to avoid unexpected # output; use the original render instead of the evaluated one. - if not isinstance(result, str): + # Complex and scientific values are also unexpected. Filter them out. + if ( + # Filter out string and complex numbers + not isinstance(result, (str, complex)) + and ( + # Pass if not numeric and not a boolean + not isinstance(result, (int, float)) + # Or it's a boolean (inherit from int) + or isinstance(result, bool) + # Or if it's a digit + or _IS_NUMERIC.match(render_result) is not None + ) + ): return result except (ValueError, TypeError, SyntaxError, MemoryError): pass diff --git a/requirements_all.txt b/requirements_all.txt index fc26d7f9f91..937d1671d5f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -303,7 +303,7 @@ aurorapy==0.2.6 av==8.0.2 # homeassistant.components.avea -# avea==1.5 +# avea==1.5.1 # homeassistant.components.avion # avion==0.10 @@ -2353,7 +2353,7 @@ zigpy-deconz==0.11.0 zigpy-xbee==0.13.0 # homeassistant.components.zha -zigpy-zigate==0.7.2 +zigpy-zigate==0.7.3 # homeassistant.components.zha zigpy-znp==0.2.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f0ab950c6dd..49f421ba3ba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1131,7 +1131,7 @@ zigpy-deconz==0.11.0 zigpy-xbee==0.13.0 # homeassistant.components.zha -zigpy-zigate==0.7.2 +zigpy-zigate==0.7.3 # homeassistant.components.zha zigpy-znp==0.2.2 diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 3c629a82012..4d049753f43 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -438,11 +438,11 @@ async def test_set_away_mode_pessimistic(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" await common.async_set_preset_mode(hass, "away", ENTITY_CLIMATE) state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async_fire_mqtt_message(hass, "away-state", "ON") state = hass.states.get(ENTITY_CLIMATE) @@ -450,11 +450,11 @@ async def test_set_away_mode_pessimistic(hass, mqtt_mock): async_fire_mqtt_message(hass, "away-state", "OFF") state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async_fire_mqtt_message(hass, "away-state", "nonsense") state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async def test_set_away_mode(hass, mqtt_mock): @@ -467,7 +467,7 @@ async def test_set_away_mode(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" await common.async_set_preset_mode(hass, "away", ENTITY_CLIMATE) mqtt_mock.async_publish.assert_called_once_with("away-mode-topic", "AN", 0, False) mqtt_mock.async_publish.reset_mock() @@ -477,7 +477,7 @@ async def test_set_away_mode(hass, mqtt_mock): await common.async_set_preset_mode(hass, PRESET_NONE, ENTITY_CLIMATE) mqtt_mock.async_publish.assert_called_once_with("away-mode-topic", "AUS", 0, False) state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" await common.async_set_preset_mode(hass, "hold-on", ENTITY_CLIMATE) mqtt_mock.async_publish.reset_mock() @@ -525,7 +525,7 @@ async def test_set_hold_pessimistic(hass, mqtt_mock): async_fire_mqtt_message(hass, "hold-state", "off") state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async def test_set_hold(hass, mqtt_mock): @@ -534,7 +534,7 @@ async def test_set_hold(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" await common.async_set_preset_mode(hass, "hold-on", ENTITY_CLIMATE) mqtt_mock.async_publish.assert_called_once_with("hold-topic", "hold-on", 0, False) mqtt_mock.async_publish.reset_mock() @@ -550,7 +550,7 @@ async def test_set_hold(hass, mqtt_mock): await common.async_set_preset_mode(hass, PRESET_NONE, ENTITY_CLIMATE) mqtt_mock.async_publish.assert_called_once_with("hold-topic", "off", 0, False) state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async def test_set_preset_mode_twice(hass, mqtt_mock): @@ -559,7 +559,7 @@ async def test_set_preset_mode_twice(hass, mqtt_mock): await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" await common.async_set_preset_mode(hass, "hold-on", ENTITY_CLIMATE) mqtt_mock.async_publish.assert_called_once_with("hold-topic", "hold-on", 0, False) mqtt_mock.async_publish.reset_mock() @@ -735,7 +735,7 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): assert state.attributes.get("temperature") == 1031 # Away Mode - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async_fire_mqtt_message(hass, "away-state", '"ON"') state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") == "away" @@ -743,7 +743,7 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): # Away Mode with JSON values async_fire_mqtt_message(hass, "away-state", "false") state = hass.states.get(ENTITY_CLIMATE) - assert state.attributes.get("preset_mode") is None + assert state.attributes.get("preset_mode") == "none" async_fire_mqtt_message(hass, "away-state", "true") state = hass.states.get(ENTITY_CLIMATE) diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py index eb9b42ea996..39a3c438cf9 100644 --- a/tests/components/onewire/__init__.py +++ b/tests/components/onewire/__init__.py @@ -48,9 +48,8 @@ async def setup_onewire_owserver_integration(hass): data={ CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", - CONF_PORT: "1234", + CONF_PORT: 1234, }, - unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234", connection_class=CONN_CLASS_LOCAL_POLL, options={}, entry_id="2", @@ -74,12 +73,11 @@ async def setup_onewire_patched_owserver_integration(hass): data={ CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", - CONF_PORT: "1234", + CONF_PORT: 1234, CONF_NAMES: { "10.111111111111": "My DS18B20", }, }, - unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234", connection_class=CONN_CLASS_LOCAL_POLL, options={}, entry_id="2", diff --git a/tests/components/onewire/test_config_flow.py b/tests/components/onewire/test_config_flow.py index dfb64a3846e..ba0ae090ed2 100644 --- a/tests/components/onewire/test_config_flow.py +++ b/tests/components/onewire/test_config_flow.py @@ -318,7 +318,7 @@ async def test_import_owserver_with_port(hass): data={ CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", - CONF_PORT: "1234", + CONF_PORT: 1234, }, ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY @@ -326,8 +326,37 @@ async def test_import_owserver_with_port(hass): assert result["data"] == { CONF_TYPE: CONF_TYPE_OWSERVER, CONF_HOST: "1.2.3.4", - CONF_PORT: "1234", + CONF_PORT: 1234, } await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import_owserver_duplicate(hass): + """Test OWServer flow.""" + # Initialise with single entry + with patch( + "homeassistant.components.onewire.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.onewire.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + await setup_onewire_owserver_integration(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + # Import duplicate entry + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_TYPE: CONF_TYPE_OWSERVER, + CONF_HOST: "1.2.3.4", + CONF_PORT: 1234, + }, + ) + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index fe2f23c0033..c8a8bc0710c 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -346,7 +346,7 @@ def test_tan(hass): (0, 0.0), (math.pi, -0.0), (math.pi / 180 * 45, 1.0), - (math.pi / 180 * 90, 1.633123935319537e16), + (math.pi / 180 * 90, "1.633123935319537e+16"), (math.pi / 180 * 135, -1.0), ("'error'", "error"), ] @@ -2416,5 +2416,22 @@ async def test_parse_result(hass): ('{{ "{{}}" }}', "{{}}"), ("not-something", "not-something"), ("2a", "2a"), + ("123E5", "123E5"), + ("1j", "1j"), + ("1e+100", "1e+100"), + ("0xface", "0xface"), + ("123", 123), + ("10", 10), + ("123.0", 123.0), + (".5", 0.5), + ("0.5", 0.5), + ("-1", -1), + ("-1.0", -1.0), + ("+1", 1), + ("5.", 5.0), + ("123_123_123", "123_123_123"), + # ("+48100200300", "+48100200300"), # phone number + ("010", "010"), + ("0011101.00100001010001", "0011101.00100001010001"), ): assert template.Template(tpl, hass).async_render() == result