I have a simple todo list, like this, and am trying to write an automation which will remove duplicates in the completed items whenever it is fired:
This is what I have, but I cannot figure out why it isn’t working:
alias: Remove duplicate items
description: ""
triggers: []
conditions: []
actions:
- action: todo.get_items
target:
entity_id: todo.test_list
data:
status:
- completed
response_variable: completed_items
- variables:
items: "{{ completed_items['todo.test_list']['items'] }}"
item_summaries: "{{ items | map(attribute='summary') | list }}"
seen: []
- repeat:
count: "{{ items | length }}"
sequence:
- variables:
item: "{{ items[repeat.index] }}"
item_summary: "{{ item_summaries[repeat.index] }}"
- choose:
- conditions:
- condition: template
value_template: "{{ item_summary not in seen }}"
sequence:
- variables:
seen: "{{ seen + [item_summary] }}"
- conditions:
- condition: template
value_template: "{{ item_summary in seen }}"
sequence:
- action: todo.remove_item
target:
entity_id: todo.test_list
data:
item: "{{ item }}"
mode: single
Basically, I am looping over every item in the list and trying to create a variable (seen
) which keeps track of whether or not we have already encountered a particular item. If not, we append it to this seen
list. If it has been seen before, then the item should be removed using todo.remove_item
.
Can anyone help?
123
(Taras)
March 25, 2025, 7:59pm
2
Currently, it doesn’t work because of scoping rules. The variable seen
you initially define is independent of the variable by the same name you define within choose
. The scope of the second instance of seen
is local to where it’s defined (in conditions
within choose
).
In the future (next month’s release) it will work because scoping rules will change.
Reference
Scope of variables
dev
← arturpragacz:helpers/script-nonlocal-variables
opened 12:50PM - 22 Mar 25 UTC
## Breaking change
The `variables` script action is no longer restricted to l… ocal scopes, it can now update the value of a variable also in outer scopes. If the variable was not previously defined, it will be created in the top-level (script run) scope.
```yaml
actions:
- variables:
x: 1
y: 1
- sequence:
- variables:
y: 2 # Updates y which exists in the outer scope
z: 2 # Since z is not defined yet, it is assigned in the top-level scope
- action: persistent_notification.create
data:
message: "{{ x }}, {{ y }}, {{ z }}" # x=1, y=2, z=2
# Note: previously it would be: x=1, y=1, z undefined
```
Users who have automations or scripts which use the same variable name in different (previously isolated) scopes will need to update them: simply use distinct variable names to prevent any conflicts.
## Proposed change
Make variables action not restricted to local scopes.
Architecture discussion: https://github.com/home-assistant/architecture/discussions/1208
This was also a popular WTH request: https://community.home-assistant.io/t/wth-cant-we-have-simple-automation-script-scope-variables/802213
## Type of change
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New integration (thank you!)
- [ ] New feature (which adds functionality to an existing integration)
- [ ] Deprecation (breaking change to happen in the future)
- [x] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
## Additional information
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request: https://github.com/home-assistant/home-assistant.io/pull/38150
- Link to developer documentation pull request:
- Link to frontend pull request:
## Checklist
- [x] The code change is tested and works locally.
- [x] Local tests pass. **Your PR cannot be merged unless tests pass**
- [x] There is no commented out code in this PR.
- [x] I have followed the [development checklist][dev-checklist]
- [x] I have followed the [perfect PR recommendations][perfect-pr]
- [x] The code has been formatted using Ruff (`ruff format homeassistant tests`)
- [x] Tests have been added to verify that the new code works.
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
If the code communicates with devices, web services, or third-party tools:
- [ ] The [manifest file][manifest-docs] has all fields filled out correctly.
Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.
Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
To help with the load of incoming pull requests:
- [ ] I have reviewed two other [open pull requests][prs] in this repository.
[prs]: https://github.com/home-assistant/core/pulls?q=is%3Aopen+is%3Apr+-author%3A%40me+-draft%3Atrue+-label%3Awaiting-for-upstream+sort%3Acreated-desc+review%3Anone+-status%3Afailure
[dev-checklist]: https://developers.home-assistant.io/docs/development_checklist/
[manifest-docs]: https://developers.home-assistant.io/docs/creating_integration_manifest/
[quality-scale]: https://developers.home-assistant.io/docs/integration_quality_scale_index/
[docs-repository]: https://github.com/home-assistant/home-assistant.io
[perfect-pr]: https://developers.home-assistant.io/docs/review-process/#creating-the-perfect-pr
1 Like
I see. Thank you, makes sense now.
Can you suggest a way to adjust it to achieve what I want (without waiting till next month )?
123
(Taras)
March 25, 2025, 8:06pm
4
I suggest you simply wait until next Wednesday (April release date).
The alternative is to find the duplicates using templating as opposed to a repeat
.
1 Like
This was the way I originally tried to do it. I assume you mean with a “for” loop inside the “data” section of the action. But I dont think we can apply the “todo.remove_item” inside a templated “for loop”. Do you agree?
That was the reason for switching to a repeat block.
123
(Taras)
March 25, 2025, 8:22pm
6
You would use the template to produce a list of duplicated items. Use repeat for_each
to iterate through the list and delete each item using todo.remove_item
.
Here’s a very basic example for producing a list of duplicates. Copy-paste it into the Template Editor and observe what it reports.
{% set x = ['cat', 'hat', 'bat', 'cat', 'rat', 'hat'] %}
{% set ns = namespace(uni=[], dup=[]) %}
{% for y in x %}
{% if y in ns.uni %}
{% set ns.dup = ns.dup + [y] %}
{% else %}
{% set ns.uni = ns.uni + [y] %}
{% endif %}
{% endfor %}
{{ ns.dup }}
You’ll need to adapt it to work with completed_items
and assign it to a variable (for example, such as duplicates
). Then you would use this:
repeat:
for_each: "{{ duplicates }}"
sequence:
- action: todo.remove_item
data:
item: "{{ repeat.item }}"
Or wait until next Wednesday.
123
(Taras)
March 26, 2025, 1:27am
7
@teeeeee
So here’s how it can be done using what I had suggested.
alias: Remove duplicate items
description: ""
triggers: []
conditions: []
actions:
- action: todo.get_items
target:
entity_id: todo.test_list
data:
status:
- completed
response_variable: completed_items
- variables:
duplicates: >
{% set x = completed_items['todo.test_list']['items'] | map(attribute='summary') | list %}
{% set ns = namespace(uni=[], dup=[]) %}
{% for y in x %}
{% if y in ns.uni %}
{% set ns.dup = ns.dup + [y] %}
{% else %}
{% set ns.uni = ns.uni + [y] %}
{% endif %}
{% endfor %}
{{ ns.dup }}
- repeat:
for_each: "{{ duplicates }}"
sequence:
- action: todo.remove_item
target:
entity_id: todo.test_list
data:
item: "{{ repeat.item }}"
mode: single
2 Likes
Excellent, this works. Thanks for helping me understand the problem.
Is the use of the namespace
type critical here? I read in the jinja documentation here that the namespace
class is related to the scope of variables. Do you use it for this reason?
1 Like
Oh actually I see what you have done. ns.uni
essentially represents the seen
variable that I had before.