From 53867201aad50aeffd23e3261f66f7d735065587 Mon Sep 17 00:00:00 2001 From: Ben Armstrong Date: Mon, 15 Jul 2024 14:55:01 -0300 Subject: [PATCH] Support `sort by` for observation queries. --- dronefly/core/parsers/constants.py | 17 +++++++++++++---- dronefly/core/parsers/unixlike.py | 6 ++++-- dronefly/core/query/query.py | 27 ++++++++++++++++++++++++--- tests/query_test.py | 8 ++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/dronefly/core/parsers/constants.py b/dronefly/core/parsers/constants.py index c86f210..ec523db 100644 --- a/dronefly/core/parsers/constants.py +++ b/dronefly/core/parsers/constants.py @@ -27,12 +27,11 @@ MACROS = { "rg": {"opt": ["quality_grade=research"]}, "nid": {"opt": ["quality_grade=needs_id"]}, - "oldest": {"opt": ["order=asc", "order_by=observed_on"]}, - "newest": {"opt": ["order=desc", "order_by=observed_on"]}, - "reverse": {"opt": ["order=asc"]}, + "oldest": {"order": "asc", "sort_by": "observed"}, + "newest": {"order": "desc", "sort_by": "observed"}, "my": {"by": "me"}, "home": {"from": "home"}, - "faves": {"opt": ["popular", "order_by=votes"]}, + "faves": {"sort_by": "votes", "opt": ["popular"]}, "spp": {"opt": ["hrank=species"]}, "species": {"opt": ["hrank=species"]}, "unseen": {"not by": "me", "from": "home"}, @@ -112,3 +111,13 @@ "without_taxon_id", "year", ] +VALID_OBS_SORT_BY = { + "added": "created_at", + "observed": "observed_on", + "votes": "votes", + # Technically these are valid values, but we are not supporting them: + # - these fields are not typically shown in Dronefly displays + # - these sort options are of limited utility & hard to explain + # "guess": "species_guess", + # "id": "id", +} diff --git a/dronefly/core/parsers/unixlike.py b/dronefly/core/parsers/unixlike.py index 8bce551..6520294 100644 --- a/dronefly/core/parsers/unixlike.py +++ b/dronefly/core/parsers/unixlike.py @@ -134,6 +134,9 @@ def parse(self, argument: str): controlled_term = [term_name, term_value] else: controlled_term = None + sort_by = None + if vals.sort_by: + sort_by = " ".join(vals.sort_by) try: obs_d1 = _parse_date_arg(vals.obs_d1, "first") obs_d2 = _parse_date_arg(vals.obs_d2, "last") @@ -159,8 +162,7 @@ def parse(self, argument: str): id_by=" ".join(vals.id_by), per=" ".join(vals.per), project=" ".join(vals.project), - sort_by=" ".join(vals.sort_by), - order=vals.order, + sort_by=sort_by, options=vals.options, obs_d1=obs_d1, obs_d2=obs_d2, diff --git a/dronefly/core/query/query.py b/dronefly/core/query/query.py index 57a3545..93c8bc8 100644 --- a/dronefly/core/query/query.py +++ b/dronefly/core/query/query.py @@ -7,7 +7,7 @@ from dronefly.core.formatters.generic import format_taxon_name, format_user_name from dronefly.core.models.controlled_terms import ControlledTermSelector -from dronefly.core.parsers.constants import VALID_OBS_OPTS +from dronefly.core.parsers.constants import VALID_OBS_OPTS, VALID_OBS_SORT_BY from pyinaturalist.models import Place, Project, Taxon, User @@ -164,8 +164,10 @@ def __str__(self): self._add_clause("added since {}", self.added_d1) self._add_clause("added until {}", self.added_d2) self._add_clause("added on {}", self.added_on) - self._add_clause("sorted by {}", self.sort_by) - self._add_clause("({self.order})", self.order) + if self.sort_by: + self._add_clause("sort by {}", self.sort_by) + if self.order: + self._add_clause("order {}", self.order) return self._query @@ -245,6 +247,8 @@ def get_base_query_args(query): _added["d1"] = query.added_d1 if has_value(query.added_d1) else None _added["d2"] = query.added_d2 if has_value(query.added_d2) else None args["added"] = DateSelector(**_added) + args["sort_by"] = query.sort_by if has_value(query.sort_by) else None + args["order"] = query.order if has_value(query.order) else None return args @@ -281,6 +285,8 @@ class QueryResponse: controlled_term: Optional[ControlledTermSelector] = None observed: Optional[DateSelector] = None added: Optional[DateSelector] = None + sort_by: Optional[str] = None + order: Optional[str] = None adjectives: Optional[List[str]] = field(init=False) def __post_init__(self): @@ -362,6 +368,10 @@ def obs_args(self): kwargs["created_d1"] = self.added.d1.isoformat() if self.added.d2: kwargs["created_d2"] = self.added.d2.isoformat() + if self.sort_by: + kwargs["order_by"] = VALID_OBS_SORT_BY.get(str(self.sort_by)) + if self.order: + kwargs["order"] = str(self.order) return kwargs def obs_query_description(self, with_adjectives: bool = True): @@ -517,4 +527,15 @@ def _format_time(time: str): message += " {} rank {} or {}".format( with_or_and, hrank or lrank, higher_or_lower ) + order_by = kwargs.get("order_by") + order = kwargs.get("order") + if order: + _order = "ascending" if order == "asc" else "descending" + message += f" in {_order} order" + if order_by: + _order_by = str(VALID_OBS_SORT_BY.get(order_by)).replace("_", " ") + if order: + message += f" by `#{_order_by}`" + else: + message += f" ordered by `{_order_by}`" return re.sub(r"^ ", "", message) diff --git a/tests/query_test.py b/tests/query_test.py index 9b23988..a038db3 100644 --- a/tests/query_test.py +++ b/tests/query_test.py @@ -105,3 +105,11 @@ def test_query_added_on(self): added_on = datetime.now() query = Query(main=TaxonQuery(terms=["birds"]), user="me", added_on=added_on) assert str(query) == "birds by me added on {}".format(added_on) + + def test_query_order_by(self): + query = Query(main=TaxonQuery(terms=["birds"]), user="me", sort_by="observed") + assert str(query) == "birds by me sort by observed" + + def test_query_order(self): + query = Query(main=TaxonQuery(terms=["birds"]), user="me", order="asc") + assert str(query) == "birds by me order asc"