Skip to content

Support path-like keys in DataTree.__contains__ - #11701

Open
Yagnik-Trivedi wants to merge 5 commits into
pydata:mainfrom
Yagnik-Trivedi:feat/9354-contains-path-like
Open

Yagnik-Trivedi wants to merge 5 commits into
pydata:mainfrom
Yagnik-Trivedi:feat/9354-contains-path-like

Conversation

@Yagnik-Trivedi

@Yagnik-Trivedi Yagnik-Trivedi commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

key in tree only looked at the current node, while tree[key] resolves unix-like paths. So tree["a/b"] worked but "a/b" in tree returned False (same for "/a/b", "./a", "a/b/var", ".", "/", ".." from a child, ...).

This PR makes __contains__ follow the same rule as indexing: key in tree is True exactly when tree[key] succeeds.

  • Plain names keep the existing fast local check (self.variables / self.children, which already include inherited coordinates).
  • String keys that are paths (contain /, or are "", ".", "..") go through the same _get_item lookup that __getitem__ uses, so the two can't drift apart again.
  • Non-string keys are unchanged.

While doing this I found that _get_item raised AttributeError instead of KeyError when a path continues past a variable, e.g. tree["group/var/x"] or tree["group/var/.."], because it kept walking into the DataArray. It now raises KeyError, which __contains__ relies on.

Performance: routing every lookup through _get_item made "var" in tree ~30x slower (it builds a DatasetView), so plain names short-circuit first. Measured on a node with 50 variables and 50 children: ~1.5 µs before and after for hits and misses.

Open questions for reviewers

  1. DataTree.get("a/b") is still local-only, as its docstring says, so in and get now differ for path keys. Should get also accept paths (separate PR)?
  2. "a/b" in tree.keys() is now True although iterating keys() only yields local names. Dataset behaves similarly for coordinates ("x" in ds but not in list(ds)), so I left it.

Checklist

  • Closes DataTree.__contains__ should support pathlike syntax #9354
  • Tests added (TestContains: relative/absolute/../self paths, variables at a path, inherited coords, paths past a variable, non-string and unhashable keys; plus a __getitem__ regression test)
  • User visible changes (including notable bug fixes) are documented in whats-new.rst (also added an example to the hierarchical-data user guide)

AI Disclosure

  • This PR contains AI-generated content.
    • I have tested any AI-generated content in my PR.
    • I take responsibility for any AI-generated content in my PR.
      Tools: Claude Code (Claude Opus 5.5). I asked it to find an open issue, reproduce it, and implement a fix covering the edge cases. I chose the issue and the approach (reuse _get_item; "", ".", "/" count as present because tree[key] returns the tree). The code, tests, and this description were written by the agent at my direction.

`key in tree` only looked at the current node, while `tree[key]` resolves
unix-like paths, so e.g. `"a/b" in tree` was False even though `tree["a/b"]`
worked. Fall back to the same path lookup as indexing for string keys
that are paths, keeping the fast local check for plain names.

Also make `_get_item` raise KeyError instead of AttributeError when a
path continues past a variable (e.g. `tree["group/var/x"]`), so that
`in` can rely on it.

Closes pydata#9354

Co-authored-by: Claude <noreply@anthropic.com>
@welcome

welcome Bot commented Oct 9, 2026

Copy link
Copy Markdown

Thank you for opening this pull request! It may take us a few days to respond here, so thank you for being patient.
If you have questions, some answers may be found in our contributing guidelines.

@github-actions github-actions Bot added the topic-DataTree Related to the implementation of a DataTree class label Oct 9, 2026
Co-authored-by: Claude <noreply@anthropic.com>

@headtr1ck headtr1ck left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, otherwise this looks good, thanks!

Comment thread xarray/core/datatree.py
if isinstance(key, str) and ("/" in key or key in ("", ".", "..")):
try:
self._get_item(key)
except KeyError:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "//a" would raise a ValueError here, maybe we should catch these errors as well?
Not 100% sure though.

@Yagnik-Trivedi Yagnik-Trivedi Oct 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right: with exactly two leading slashes, POSIX path rules keep // as a separate root, and NodePath rejected it with a ValueError, which leaked out of in. Since "///a" and "a//b" are already normalized, I made NodePath treat a leading // like / as well (8e6b90f). So now "//a" in tree is True and tree["//a"] returns the same node as tree["/a"]. Tests added for both.

Yagnik-Trivedi and others added 3 commits October 11, 2026 08:39
…ins__

Paths starting with exactly two slashes (e.g. "//a") are rejected by
NodePath with a ValueError, which leaked out of `in`.

Co-authored-by: Claude <noreply@anthropic.com>
POSIX keeps exactly two leading slashes as a distinct root, so NodePath
rejected paths like "//a" with a ValueError, while "/a" and "///a" both
worked. Normalize it in NodePath, so indexing, `in`, from_dict and
assignment all accept it, and drop the ValueError catch in
__contains__ that is no longer needed.

Co-authored-by: Claude <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic-DataTree Related to the implementation of a DataTree class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DataTree.__contains__ should support pathlike syntax

2 participants