Skip to content

Commit

Permalink
taxon list: support per <rank>
Browse files Browse the repository at this point in the history
  • Loading branch information
synrg committed Aug 13, 2024
1 parent b20ef78 commit 3693235
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 12 deletions.
1 change: 1 addition & 0 deletions inatcog/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def __init__(self, *args, **kwargs):
self.projects.add_users = asyncify(self, self.projects.add_users)
self.projects.delete_users = asyncify(self, self.projects.delete_users)
self.taxa.populate = asyncify(self, self.taxa.populate)
self.taxa.search = asyncify(self, self.taxa.search)
self.observations.taxon_summary = asyncify(
self, self.observations.taxon_summary
)
Expand Down
2 changes: 1 addition & 1 deletion inatcog/commands/obs.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ async def life(self, ctx, *, query: Optional[Union[TaxonReplyConverter, str]]):
• Buttons to change taxon row details:
• :regional_indicator_d: toggles direct taxon count.
• :regional_indicator_c: toggles common names (user life list only).
• Use `per any` for maximum detail or `per <rank>` to show taxa of just this rank.
• Use `per any` for maximum detail or `per <rank>` to show taxa at that rank's level.
• Use `sort by obs` to sort by #obs instead of name.
• Use `asc` or `desc` to sort ascending or descending.
• See `[p]query` and `[p]taxon_query` for help with *query* terms, or `[p]glossary` for an explanation of *leaf taxa*.
Expand Down
77 changes: 66 additions & 11 deletions inatcog/commands/taxon.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
format_taxon_name,
format_taxon_establishment_means,
)
from dronefly.core.constants import TRACHEOPHYTA_ID
from dronefly.core.constants import RANKS_FOR_LEVEL, RANK_KEYWORDS, TRACHEOPHYTA_ID
from dronefly.core.formatters.generic import TaxonListFormatter
from dronefly.discord.embeds import make_embed, MAX_EMBED_DESCRIPTION_LEN
from dronefly.discord.menus import TaxonListMenu
from pyinaturalist import RANK_EQUIVALENTS, RANK_LEVELS
from redbot.core import checks, commands
from redbot.core.commands import BadArgument

Expand Down Expand Up @@ -83,38 +84,92 @@ async def taxon(self, ctx, *, query: Optional[str]):
async def taxon_list(self, ctx, *, query: Optional[str]):
"""List a taxon's children.
- *Taxon query terms* match a single taxon to display.
- *Reply* to another display to display its taxon.
- The *query* is optional when that display contains a taxon.
• *Taxon query terms* match a single taxon to display.
• Use `per <rank>` to show descendant taxon at that rank's level; `per child` is the default.
• *Reply* to another display to display its taxon.
• The *query* is optional when that display contains a taxon.
**Related help topics:**
- `[p]taxon_query` for *taxon query* terms
- `[p]help s taxa` to search and browse matching taxa
"""
`[p]taxon_query` for *taxon query* terms
`[p]help s taxa` to search and browse matching taxa
""" # noqa: E501
error_msg = None
msg = None
async with self._get_taxon_response(ctx, query) as (query_response, _query):
if not query_response:
return
try:
per_rank = "child"
short_description = "Children"
per_rank = _query.per or "child"
if per_rank not in [*RANK_KEYWORDS, "child"]:
raise BadArgument(
"Specify `per <rank-or-keyword>`. "
f"See `{ctx.clean_prefix}help taxon list` for details."
)
short_description = self.p.plural(per_rank).capitalize()
taxon = query_response.taxon
if not taxon.children:
taxon = await ctx.inat_client.taxa.populate(taxon)
if not taxon.children:
raise LookupError(f"{taxon.name} has no child taxa")
taxon_list = [taxon, *taxon.children]
taxon_list = [
taxon,
*[_taxon for _taxon in taxon.children if _taxon.is_active],
]
per_page = 10
sort_by = _query.sort_by or None
if sort_by not in [None, "obs", "name"]:
raise BadArgument(
"Specify `sort by obs` or `sort by name` (default)"
f"See `{ctx.clean_prefix}help taxon list` for details."
)
_per_rank = per_rank
if per_rank != "child":
_per_rank = RANK_EQUIVALENTS.get(per_rank) or per_rank
rank_level = RANK_LEVELS[_per_rank]
if rank_level >= taxon.rank_level:
raise BadArgument(
f"The rank `{per_rank}` is not lower than "
f"the taxon rank: `{taxon.rank}`."
)
_children = [
child for child in taxon_list if child.rank_level == rank_level
]
_without_rank_ids = [
child.id for child in taxon_list if child not in _children
]
if len(_without_rank_ids) > 0:
# One chance at retrieving the remaining children, i.e. if the
# remainder (direct children - those at the specified rank level)
# don't constitute a single page of results, then show children
# instead.
_descendants = await ctx.inat_client.taxa.search(
taxon_id=_without_rank_ids,
rank_level=rank_level,
is_active=True,
per_page=500,
)
# The choice of 2500 as our limit is arbitrary:
# - will take 5 more API calls to satisfy
# - encompasses the largest genera (e.g. Astragalus)
# - meant to limit unreasonable sized queries so they don't make
# excessive API demands
# - TODO: switch to using a local DB built from full taxonomy dump
# so we can lift this restriction
if _descendants.count() > 2500:
short_description = "Children"
await ctx.send(
f"Too many {self.p.plural(_per_rank)}. "
"Listing children instead."
)
_per_rank = "child"
else:
taxon_list = [taxon, *_children, *_descendants.all()]
if _per_rank != "child":
# List all ranks at the same level, not just the specified rank
_per_rank = RANKS_FOR_LEVEL[rank_level]
order = _query.order or None
taxon_list_formatter = TaxonListFormatter(
taxon_list,
per_rank,
_per_rank,
query_response,
with_taxa=True,
per_page=per_page,
Expand Down

0 comments on commit 3693235

Please sign in to comment.