From f7fb2bf2710f16e8032f2dbfa46aacbf1b61be6d Mon Sep 17 00:00:00 2001 From: Jordan Cook Date: Mon, 22 Jul 2024 21:11:44 -0500 Subject: [PATCH] Add shortcut properties to Taxon for ancestors of common ranks --- HISTORY.md | 2 ++ pyinaturalist/models/taxon.py | 27 +++++++++++++++++++++++++++ test/test_models.py | 10 ++++++++++ 3 files changed, 39 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 1d5bd4b8..6bd8fc7c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,6 +10,8 @@ * 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 +* 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 ### Rate limits, timeouts, and error handling diff --git a/pyinaturalist/models/taxon.py b/pyinaturalist/models/taxon.py index 11bb8466..194bb58a 100644 --- a/pyinaturalist/models/taxon.py +++ b/pyinaturalist/models/taxon.py @@ -186,6 +186,33 @@ def from_sorted_json_list(cls, value: JsonResponse, **kwargs) -> List['Taxon']: taxa.sort(key=_sort_rank_name) return taxa + @property + def kingdom(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('kingdom') + + @property + def phylum(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('phylum') + + @property + def class_(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('class') + + @property + def order(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('order') + + @property + def family(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('family') + + @property + def genus(self) -> Optional['Taxon']: + return self._get_ancestor_by_rank('genus') + + def _get_ancestor_by_rank(self, rank: str) -> Optional['Taxon']: + return next((t for t in self.ancestors if t.rank == rank), None) + @property def child_ids(self) -> List[int]: """Taxon IDs of direct children, sorted by rank then name""" diff --git a/test/test_models.py b/test/test_models.py index 36694449..73c3e438 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -938,6 +938,16 @@ def test_taxon__ancestor_ids_from_ancestor_objs(): assert taxon.ancestor_ids == ids +def test_taxon_ancestor_shortcuts(): + taxon = Taxon.from_json(j_taxon_1) + assert taxon.kingdom.id == 1 and taxon.kingdom.name == 'Animalia' + assert taxon.phylum.id == 47120 and taxon.phylum.name == 'Arthropoda' + assert taxon.class_.id == 47158 and taxon.class_.name == 'Insecta' + assert taxon.order.id == 47208 and taxon.order.name == 'Coleoptera' + assert taxon.family.id == 53849 and taxon.family.name == 'Silphidae' + assert taxon.genus.id == 53850 and taxon.genus.name == 'Nicrophorus' + + def test_taxon__all_names(): taxon = Taxon.from_json(j_taxon_8_all_names) assert taxon.names[1] == {