Rejectattr filter

Hi,

I’m using the following template in an automation that puts together a list of entity_ids of lights that match the criteria.

{% set lights_on_lt_day_threshold = expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
   | selectattr("state", "eq", "on")
   | selectattr("attributes.brightness", "le", 204)
   | map(attribute="entity_id") | list | join(", ") %}

I’d like to be able to filter out the entity “light.bathroom_pendant”, I’ve tried:

{% set filter = ['light.bathroom_pendant'] %}
{{ lights_on_lt_day_threshold | rejectattr('entity_id', in, filter) }}

But this results in:

<generator object select_or_reject at 0x7f9e802580>

Anyone have any pointers? Thanks!

If you want a list, just add |list. A generator is a function that produces the additional items each time it’s called, and you can tell Jinja to form a list from a generator.

Thanks, I tried adding | list as you suggested:

{{ lights_on_lt_day_threshold | rejectattr('entity_id', in, filter) | list }}

Results in:

TemplateRuntimeError: No test named Undefined. ('in' is undefined; did you forget to quote the callable name?)

The answer is right there - you need to surround the test in rejectattr in quotes.

{{ lights_on_lt_day_threshold | rejectattr('entity_id', 'in', filter) | list }}

Here’s an example that I put together:

{% set switches = ['switch.fr_table_lamp', 'switch.fr_reading_lamp'] %}
{{ states.switch|selectattr('entity_id', 'in', switches)|selectattr('state', '==', 'off')|map(attribute="name")|list }}

I think the bigger problem that you’ll run into is that you’re not feeding a list of objects into that statement, only a comma-separated list of entity_id strings. rejectattr expects to act on objects, so I would expect filtering on entity_id to fail. The fix should be easy, though:

{% set lights_on_lt_day_threshold = expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
   | selectattr("state", "eq", "on")
   | selectattr("attributes.brightness", "le", 204) %}

{% set filter = ['light.bathroom_pendant'] %}

{{ lights_on_lt_day_threshold | rejectattr('entity_id', 'in', filter) | list }}
4 Likes

Great, thank you for explaining this :+1:

I assume you provided a simplified example and the real one is more complicated? Otherwise it seems odd to explicitly include light.bathroom_pendant within expand() and then, within the same template, look for a way to exclude it.

For example, if the real-world example begins by selecting all lights that are on, from all available lights, then excluding a few (from a reject list), it would be a sensible use-case for reject (or rejectattr).

{{ states.light
   | selectattr('state', 'eq', 'on')
   | map(attribute='entity_id')
   | reject('in', ['light.kitchen', 'light.bathroom'])
   | join(', ') }}

Alternately, if there are two groups where one contains many lights and the other represents the reject list:

{{ expand('group.first_floor_lights')
   | selectattr('state', 'eq', 'on')
   | map(attribute='entity_id')
   | reject('in', expand('group.rejected_lights')| map(attribute='entity_id'))
   | join(', ') }}

Yes significantly more complicated (probably embarrassingly so). I couldn’t actually find a way to get rejectattr to work. Here’s the automation, I’ve split it into three as it’s so long. It works but the middle section does cause errors in the logs, but I’m not sure how else I could do it:

# Default brightness
- alias: default_brightness
  mode: parallel
  max: 100
  trigger:
    - platform: homeassistant
      event: start
    - platform: sun
      event: sunrise
    - platform: sun
      event: sunset
    - platform: state
      entity_id: 
        - light.hallway_pendant_zha
        - light.bathroom_pendant_zha
        - light.bedroom_pendant_zha
  action:
    - wait_template: >-
        {% set ignore = ["unknown", "unavailable"] %}
        {{ states("light.hallway_pendant_zha") not in ignore
           and states("light.bathroom_pendant_zha") not in ignore
           and states("light.bedroom_pendant_zha") not in ignore
           and states("light.hallway_pendant") not in ignore
           and states("light.bathroom_pendant") not in ignore
           and states("light.bedroom_pendant") not in ignore }}
    - variables:
        # 50% brightness
        night_threshold: 128
        # 80% brightness
        day_threshold: 204
        # Lights on <= day threshold 80% brightness
        lights_on_lt_day_threshold: >
          {{ expand("light.hallway_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "le", day_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        # Lights on >= night threshold 50% brightness
        lights_on_gt_night_threshold: >
          {{ expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "ge", night_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
    - choose:
      # IF triggered by a light turning on
      - conditions:
        - condition: template
          value_template: >-
            {% set domain = "light" %}
            {{ trigger.entity_id in states[domain] | map(attribute="entity_id") | list }}
        - condition: template
          value_template: '{{ trigger.from_state.state == "off" }}'
        sequence:
          - service: light.turn_on
            data_template:
              entity_id: >-
                {% if "_zha" in trigger.entity_id %}
                  {{ trigger.entity_id.split("_zha")[0] }}
                {% else %}
                  {{ trigger.entity_id }}
                {% endif %}
              brightness_pct: >-
                {% if is_state("sun.sun", "above_horizon") %}
                  {% if trigger.entity_id == "light.bathroom_pendant_zha" %}
                    100
                  {% else %}
                    80
                  {% endif %}
                {% elif is_state("sun.sun", "below_horizon") %}
                  50
                {% endif %}
      # IF triggered by sunrise or ha start and sun is above the horizon
      - conditions:
        - condition: or
          conditions:
            - condition: template
              value_template: '{{ trigger.event == "sunrise" }}'
            - condition: template
              value_template: '{{ trigger.event == "start" and is_state("sun.sun", "above_horizon") }}'
        sequence:
          - service: light.turn_on
            data_template:
              entity_id: >
                {{ lights_on_lt_day_threshold if lights_on_lt_day_threshold | length > 0 else "none" }}
              brightness_pct: 80
              transition: 300
          - service_template: light.turn_on
            data_template:
              entity_id: >
                {{ light.bathroom_pendant if is_state("light.bathroom_pendant", "on")
                   and state_attr("light.bathroom_pendant", "brightness") < 255  else "none" }}
              brightness_pct: 100
              transition: 300
      # IF triggered by sunset or ha start and sun is below the horizon
      - conditions:
        - condition: or
          conditions:
            - condition: template
              value_template: '{{ trigger.event == "sunset" }}'
            - condition: template
              value_template: '{{ trigger.event == "start" and is_state("sun.sun", "below_horizon") }}'
        - condition: template
          value_template: '{{ lights_on_gt_night_threshold | length > 0 }}'
        sequence:
          - service: light.turn_on
            data_template:
              entity_id: '{{ lights_on_gt_night_threshold }}'
              brightness_pct: 50
              transition: 300

Where is rejectattr used in all of that? If it’s already in there, I am embarrassed to say I couldn’t find it …

BTW, what is this supposed to do?

{% set domain = "light" %}
{{ trigger.entity_id in states[domain] | map(attribute="entity_id") | list }}

It seems unnecessary.

It’s not :confused: I was hoping to use it in the middle section to exclude light.bathroom_pendant from lights_on_lt_day_threshold as I want light.bathroom_pendant to have a default daytime turn on brightness of 100% but for all my other lights it will be 80%.

In the end I just removed light.bathroom_pendant from lights_on_lt_day_threshold and deal with it separately.

I’ll definitely use rejectattr() / reject() at some point, so thank you for explaining none the less.

That condition is unnecessary (basically is the trigger a light) so I’ll take it out. Still learning :flushed:

FWIW, I refactored your automation, employing Template Conditions in shorthand notation and the new target option for service calls.

# Default brightness
- alias: default_brightness
  mode: parallel
  max: 100
  trigger:
    - platform: homeassistant
      event: start
    - platform: sun
      event: sunrise
    - platform: sun
      event: sunset
    - platform: state
      entity_id: 
        - light.hallway_pendant_zha
        - light.bathroom_pendant_zha
        - light.bedroom_pendant_zha
  action:
    - wait_template: >-
        {% set ignore = ["unknown", "unavailable"] %}
        {{ states("light.hallway_pendant_zha") not in ignore
           and states("light.bathroom_pendant_zha") not in ignore
           and states("light.bedroom_pendant_zha") not in ignore
           and states("light.hallway_pendant") not in ignore
           and states("light.bathroom_pendant") not in ignore
           and states("light.bedroom_pendant") not in ignore }}
    - variables:
        # 50% brightness
        night_threshold: 128
        # 80% brightness
        day_threshold: 204
        # Lights on <= day threshold 80% brightness
        lights_on_lt_day_threshold: >
          {{ expand("light.hallway_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "le", day_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        # Lights on >= night threshold 50% brightness
        lights_on_gt_night_threshold: >
          {{ expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "ge", night_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        sun_above: '{{ is_state("sun.sun", "above_horizon") }}'
        sun_below: '{{ is_state("sun.sun", "below_horizon") }}'
    - choose:
      # IF triggered by a light turning on
      - conditions: '{{ trigger.platform == "state" and trigger.from_state.state == "off" }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ trigger.entity_id.split("_zha")[0] if "_zha" in trigger.entity_id else trigger.entity_id }}'
            data:
              brightness_pct: >-
                {% if sun_above %} {{ 100 if trigger.entity_id == "light.bathroom_pendant_zha" else 80 }}
                {% else %} 50
                {% endif %}
      # IF triggered by sunrise or ha start and sun is above the horizon
      - conditions: '{{ trigger.event in ["sunrise", "start"] and sun_above }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ lights_on_lt_day_threshold if lights_on_lt_day_threshold | length > 0 else "none" }}'
            data:
              brightness_pct: 80
              transition: 300
          - service: light.turn_on
            target:
              entity_id: >
                {{ light.bathroom_pendant if is_state("light.bathroom_pendant", "on")
                   and state_attr("light.bathroom_pendant", "brightness") < 255  else "none" }}
            data:
              brightness_pct: 100
              transition: 300
      # IF triggered by sunset or ha start and sun is below the horizon
      - conditions:
        - '{{ trigger.event in ["sunset", "start"] and sun_below }}'
        - '{{ lights_on_gt_night_threshold | length > 0 }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ lights_on_gt_night_threshold }}'
            data:
              brightness_pct: 50
              transition: 300

Where in this version do you want to reject one of the lights?

Wow! Thank you, that’s so much tidier :+1:

I don’t think I do, my way of ‘rejecting’ light.bathroom_pendant which has a different daytime default brightness was to remove it from lights_on_lt_day_threshold. I’m not sure how to explain so here’s a screenshot.

Previously light.bathroom_pendant was in lights_on_lt_day_threshold and I was planning on using rejectattr() / reject() so that I could set a different brightness for it compared to the other lights in the lights_on_lt_day_threshold list, but settled on this method instead.

Also, can I ask - probably a really dumb question, but - what’s better/different about using target for service calls rather than data or data-template?

You’ll be pleased to know I complicated the automation main even further by adding an additional default brightness for later on in the evening:

# Default brightness
- alias: default_brightness
  mode: parallel
  max: 100
  trigger:
    - platform: homeassistant
      event: start
    - platform: sun
      event: sunrise
    - platform: sun
      event: sunset
    - platform: template
      value_template: >
        {% set now_time = now().timestamp() | timestamp_custom("%H:%M", true) %}
        {% set sunset_offset_1hr = (as_timestamp(state_attr("sun.sun", "next_setting")) + (60 * 60)) | timestamp_custom("%H:%M", true) %}
        {% set sunrise_time = as_timestamp(state_attr("sun.sun", "next_rising")) | timestamp_custom("%H:%M", true) %}
        {{ ("21:00" <= now_time <= "23:59") or ("00:00" <= now_time <= sunrise_time)
           if sunset_offset_1hr < "21:00" 
           else (sunset_offset_1hr <= now_time <= "23:59") or ("00:00" <= now_time <= sunrise_time) }}
    - platform: state
      entity_id: 
        - light.hallway_pendant_zha
        - light.bathroom_pendant_zha
        - light.bedroom_pendant_zha
  action:
    - wait_template: >-
        {% set ignore = ["unknown", "unavailable"] %}
        {{ states("light.hallway_pendant_zha") not in ignore
           and states("light.bathroom_pendant_zha") not in ignore
           and states("light.bedroom_pendant_zha") not in ignore
           and states("light.hallway_pendant") not in ignore
           and states("light.bathroom_pendant") not in ignore
           and states("light.bedroom_pendant") not in ignore }}
    - variables:
        # Is it 1 hour after sunset or 21:00 (whichever comes first) and before sunrise?
        late_night: >
          {% set now_time = now().timestamp() | timestamp_custom("%H:%M", true) %}
          {% set sunset_offset_1hr = (as_timestamp(state_attr("sun.sun", "next_setting")) + (60 * 60)) | timestamp_custom("%H:%M", true) %}
          {% set sunrise_time = as_timestamp(state_attr("sun.sun", "next_rising")) | timestamp_custom("%H:%M", true) %}
          {{ ("21:00" <= now_time <= "23:59") or ("00:00" <= now_time <= sunrise_time)
             if sunset_offset_1hr < "21:00" 
             else (sunset_offset_1hr <= now_time <= "23:59") or ("00:00" <= now_time <= sunrise_time) }}
        # 80% brightness
        day_threshold: 204
        # 50% brightness
        night_threshold: 128
        # 25% brightness
        late_night_threshold: 77
        # Lights on <= day threshold 80% brightness
        lights_on_lt_day_threshold: >
          {{ expand("light.hallway_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "le", day_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        # Lights on >= night threshold 50% brightness
        lights_on_gt_night_threshold: >
          {{ expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "ge", night_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        # Lights on >= late night threshold 25% brightness
        lights_on_gt_late_night_threshold: >
          {{ expand("light.hallway_pendant", "light.bathroom_pendant", "light.bedroom_pendant")
             | selectattr("state", "eq", "on")
             | selectattr("attributes.brightness", "ge", late_night_threshold)
             | map(attribute="entity_id") | list | join(", ") }}
        # Sun above or below horizon?
        sun_above: '{{ is_state("sun.sun", "above_horizon") }}'
        sun_below: '{{ is_state("sun.sun", "below_horizon") }}'
    - choose:
      # IF triggered by a light turning on
      - conditions: '{{ trigger.platform == "state" and trigger.from_state.state == "off" }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ trigger.entity_id.split("_zha")[0] if "_zha" in trigger.entity_id else trigger.entity_id }}'
            data:
              brightness_pct: >-
                {% if sun_above %}
                  {{ 100 if trigger.entity_id == "light.bathroom_pendant_zha" else 80 }}
                {% else %}
                  {{ 30 if late_night else 50 }}
                {% endif %}
      # IF triggered by sunrise or ha start and sun is above the horizon
      - conditions: '{{ trigger.event in ["sunrise", "start"] and sun_above }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ lights_on_lt_day_threshold if lights_on_lt_day_threshold | length > 0 else "none" }}'
            data:
              brightness_pct: 80
              transition: 300
          - service: light.turn_on
            target:
              entity_id: >
                {{ light.bathroom_pendant if is_state("light.bathroom_pendant", "on")
                   and state_attr("light.bathroom_pendant", "brightness") < 255  else "none" }}
            data:
              brightness_pct: 100
              transition: 300
      # IF triggered by sunset, ha start or late_night trigger template and sun is below the horizon
      - conditions:
        - '{{ trigger.event in ["sunset", "start"] or trigger.platform == "template" and sun_below }}'
        - '{{ lights_on_gt_late_night_threshold | length > 0 if late_night else lights_on_gt_night_threshold | length > 0 }}'
        sequence:
          - service: light.turn_on
            target:
              entity_id: '{{ lights_on_gt_late_night_threshold if late_night else lights_on_gt_night_threshold }}'
            data:
              brightness_pct: '{{ 30 if late_night else 50 }}'
              transition: 300