diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 86a2242944d..555a268b62a 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -51,6 +51,7 @@ from .const import ( CONF_DB_NAME, CONF_DEFAULT_MEASUREMENT, CONF_HOST, + CONF_IGNORE_ATTRIBUTES, CONF_ORG, CONF_OVERRIDE_MEASUREMENT, CONF_PASSWORD, @@ -142,7 +143,10 @@ def validate_version_specific_config(conf: Dict) -> Dict: _CUSTOMIZE_ENTITY_SCHEMA = vol.Schema( - {vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string} + { + vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string, + vol.Optional(CONF_IGNORE_ATTRIBUTES): vol.All(cv.ensure_list, [cv.string]), + } ) _INFLUX_BASE_SCHEMA = INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend( @@ -154,6 +158,9 @@ _INFLUX_BASE_SCHEMA = INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend( vol.Optional(CONF_TAGS_ATTRIBUTES, default=[]): vol.All( cv.ensure_list, [cv.string] ), + vol.Optional(CONF_IGNORE_ATTRIBUTES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), vol.Optional(CONF_COMPONENT_CONFIG, default={}): vol.Schema( {cv.entity_id: _CUSTOMIZE_ENTITY_SCHEMA} ), @@ -182,6 +189,7 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: tags_attributes = conf.get(CONF_TAGS_ATTRIBUTES) default_measurement = conf.get(CONF_DEFAULT_MEASUREMENT) override_measurement = conf.get(CONF_OVERRIDE_MEASUREMENT) + global_ignore_attributes = set(conf[CONF_IGNORE_ATTRIBUTES]) component_config = EntityValues( conf[CONF_COMPONENT_CONFIG], conf[CONF_COMPONENT_CONFIG_DOMAIN], @@ -211,9 +219,8 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: _include_state = True include_uom = True - measurement = component_config.get(state.entity_id).get( - CONF_OVERRIDE_MEASUREMENT - ) + entity_config = component_config.get(state.entity_id) + measurement = entity_config.get(CONF_OVERRIDE_MEASUREMENT) if measurement in (None, ""): if override_measurement: measurement = override_measurement @@ -241,10 +248,14 @@ def _generate_event_to_json(conf: Dict) -> Callable[[Dict], str]: if _include_value: json[INFLUX_CONF_FIELDS][INFLUX_CONF_VALUE] = _state_as_value + ignore_attributes = set(entity_config.get(CONF_IGNORE_ATTRIBUTES, [])) + ignore_attributes.update(global_ignore_attributes) for key, value in state.attributes.items(): if key in tags_attributes: json[INFLUX_CONF_TAGS][key] = value - elif key != CONF_UNIT_OF_MEASUREMENT or include_uom: + elif ( + key != CONF_UNIT_OF_MEASUREMENT or include_uom + ) and key not in ignore_attributes: # If the key is already in fields if key in json[INFLUX_CONF_FIELDS]: key = f"{key}_" diff --git a/homeassistant/components/influxdb/const.py b/homeassistant/components/influxdb/const.py index 1c7a9a0bfaa..a9115c3fc68 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -28,6 +28,7 @@ CONF_COMPONENT_CONFIG = "component_config" CONF_COMPONENT_CONFIG_GLOB = "component_config_glob" CONF_COMPONENT_CONFIG_DOMAIN = "component_config_domain" CONF_RETRY_COUNT = "max_retries" +CONF_IGNORE_ATTRIBUTES = "ignore_attributes" CONF_LANGUAGE = "language" CONF_QUERIES = "queries" diff --git a/tests/components/influxdb/test_init.py b/tests/components/influxdb/test_init.py index 02a98a527ac..7e2a9f5d9c3 100644 --- a/tests/components/influxdb/test_init.py +++ b/tests/components/influxdb/test_init.py @@ -1059,6 +1059,148 @@ async def test_event_listener_component_override_measurement( write_api.reset_mock() +@pytest.mark.parametrize( + "mock_client, config_ext, get_write_api, get_mock_call", + [ + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + _get_write_api_mock_v1, + influxdb.DEFAULT_API_VERSION, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + _get_write_api_mock_v2, + influxdb.API_VERSION_2, + ), + ], + indirect=["mock_client", "get_mock_call"], +) +async def test_event_listener_ignore_attributes( + hass, mock_client, config_ext, get_write_api, get_mock_call +): + """Test the event listener with overridden measurements.""" + config = { + "ignore_attributes": ["ignore"], + "component_config": { + "sensor.fake_humidity": {"ignore_attributes": ["id_ignore"]} + }, + "component_config_glob": { + "binary_sensor.*motion": {"ignore_attributes": ["glob_ignore"]} + }, + "component_config_domain": { + "climate": {"ignore_attributes": ["domain_ignore"]} + }, + } + config.update(config_ext) + handler_method = await _setup(hass, mock_client, config, get_write_api) + + test_components = [ + { + "domain": "sensor", + "id": "fake_humidity", + "attrs": {"glob_ignore": 1, "domain_ignore": 1}, + }, + { + "domain": "binary_sensor", + "id": "fake_motion", + "attrs": {"id_ignore": 1, "domain_ignore": 1}, + }, + { + "domain": "climate", + "id": "fake_thermostat", + "attrs": {"id_ignore": 1, "glob_ignore": 1}, + }, + ] + for comp in test_components: + entity_id = f"{comp['domain']}.{comp['id']}" + state = MagicMock( + state=1, + domain=comp["domain"], + entity_id=entity_id, + object_id=comp["id"], + attributes={ + "ignore": 1, + "id_ignore": 1, + "glob_ignore": 1, + "domain_ignore": 1, + }, + ) + event = MagicMock(data={"new_state": state}, time_fired=12345) + fields = {"value": 1} + fields.update(comp["attrs"]) + body = [ + { + "measurement": entity_id, + "tags": {"domain": comp["domain"], "entity_id": comp["id"]}, + "time": 12345, + "fields": fields, + } + ] + handler_method(event) + hass.data[influxdb.DOMAIN].block_till_done() + + write_api = get_write_api(mock_client) + assert write_api.call_count == 1 + assert write_api.call_args == get_mock_call(body) + write_api.reset_mock() + + +@pytest.mark.parametrize( + "mock_client, config_ext, get_write_api, get_mock_call", + [ + ( + influxdb.DEFAULT_API_VERSION, + BASE_V1_CONFIG, + _get_write_api_mock_v1, + influxdb.DEFAULT_API_VERSION, + ), + ( + influxdb.API_VERSION_2, + BASE_V2_CONFIG, + _get_write_api_mock_v2, + influxdb.API_VERSION_2, + ), + ], + indirect=["mock_client", "get_mock_call"], +) +async def test_event_listener_ignore_attributes_overlapping_entities( + hass, mock_client, config_ext, get_write_api, get_mock_call +): + """Test the event listener with overridden measurements.""" + config = { + "component_config": {"sensor.fake": {"override_measurement": "units"}}, + "component_config_domain": {"sensor": {"ignore_attributes": ["ignore"]}}, + } + config.update(config_ext) + handler_method = await _setup(hass, mock_client, config, get_write_api) + + state = MagicMock( + state=1, + domain="sensor", + entity_id="sensor.fake", + object_id="fake", + attributes={"ignore": 1}, + ) + event = MagicMock(data={"new_state": state}, time_fired=12345) + body = [ + { + "measurement": "units", + "tags": {"domain": "sensor", "entity_id": "fake"}, + "time": 12345, + "fields": {"value": 1}, + } + ] + handler_method(event) + hass.data[influxdb.DOMAIN].block_till_done() + + write_api = get_write_api(mock_client) + assert write_api.call_count == 1 + assert write_api.call_args == get_mock_call(body) + write_api.reset_mock() + + @pytest.mark.parametrize( "mock_client, config_ext, get_write_api, get_mock_call", [