From 171d1800f405b49fbc97af16d3c7f9222efff72b Mon Sep 17 00:00:00 2001 From: Jordan Cook Date: Tue, 13 Aug 2024 13:10:09 -0500 Subject: [PATCH] Handle case when all available ranks are filtered out in make_tree() --- HISTORY.md | 5 +++-- pyinaturalist/models/taxon.py | 7 +++++-- test/test_models.py | 9 +++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 6768b1e5..fd6ba70e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,8 +9,9 @@ * Add `User.annotated_observations_count` field * Add `root_id` filter to `taxon.make_tree()` to explicitly set the root taxon instead of determining it automatically * Fix `taxon.make_tree()` rank filtering to allow skipping any number of rank levels -* `taxon.make_tree()` now returns copies of original taxon objects instead of modifying them in-place -* `Taxon.flatten(hide_root=True)` now only hides the root taxon if it was automatically inserted by `make_tree()` +* Fix `taxon.make_tree()` rank filtering to handle all available ranks filtered out +* Update `taxon.make_tree()` to return copies of original taxon objects instead of modifying them in-place +* Update `Taxon.flatten(hide_root=True)` to only hide the root taxon if it was automatically inserted by `make_tree()` * Add shortcut properties to `Taxon` for ancestors of common ranks: `Taxon.kingdom`, `phylum`, `class_` (note the `_`; 'class' is a reserved keyword), `order`, `family`, `genus` * Update `Observation.taxon.ancestors` based on identification data, if available diff --git a/pyinaturalist/models/taxon.py b/pyinaturalist/models/taxon.py index a46818fd..e569cc33 100644 --- a/pyinaturalist/models/taxon.py +++ b/pyinaturalist/models/taxon.py @@ -522,8 +522,11 @@ def _find_root( def _find_and_graft_root(taxa: Iterable[Taxon], include_ranks: Optional[List[str]] = None) -> Taxon: taxa_by_id = {t.id: t for t in taxa} - max_rank = max(t.rank_level for t in taxa if not include_ranks or t.rank in include_ranks) - root_taxa = [t for t in taxa if t.rank_level == max_rank] + rank_levels = [t.rank_level for t in taxa if not include_ranks or t.rank in include_ranks] + if not rank_levels: + logger.warning('All taxon ranks excluded; returning default root') + return deepcopy(DEFAULT_ROOT) + root_taxa = [t for t in taxa if t.rank_level == max(rank_levels)] # Add any ungrafted taxa and deduplicate ungrafted = [ diff --git a/test/test_models.py b/test/test_models.py index 3261e7a4..b0d035cd 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -1205,6 +1205,15 @@ def test_make_tree__filtered(): ] +def test_make_tree__all_filtered(): + """When all available ranks are filtered out, a single root node should be created""" + root = make_tree( + Taxon.from_json_list(j_life_list_2), + include_ranks=['infraclass'], + ) + assert root.name == 'Life' and not root.children + + def test_make_tree__preserves_originals(): """Children/ancestors of original taxon objects should be preserved""" taxa = Taxon.from_json_list(j_life_list_2)