Skip to content

Commit ce0851b

Browse files
authored
Add faceting/filtering to association endpoints (#786)
Updates association endpoints to allow facet field, facet query and (open) filter query params
1 parent a38620d commit ce0851b

35 files changed

+423
-139
lines changed

backend/src/monarch_py/api/association.py

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ async def _get_associations(
2929
object_taxon: Union[List[str], None] = Query(default=None),
3030
entity: Union[List[str], None] = Query(default=None),
3131
direct: bool = Query(default=False),
32+
facet_fields: List[str] = Query(default_factory=list),
33+
facet_queries: List[str] = Query(default_factory=list),
34+
filter_queries: List[str] = Query(default_factory=list),
3235
compact: bool = Query(default=False),
3336
pagination: PaginationParams = Depends(),
3437
format: OutputFormat = Query(
@@ -52,6 +55,9 @@ async def _get_associations(
5255
object_namespace=object_namespace,
5356
direct=direct,
5457
compact=compact,
58+
facet_fields=facet_fields,
59+
facet_queries=facet_queries,
60+
filter_queries=filter_queries,
5561
offset=pagination.offset,
5662
limit=pagination.limit,
5763
)

backend/src/monarch_py/api/entity.py

+14
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ def _association_table(
8686
title="Only return direct associations",
8787
examples=[True, False],
8888
),
89+
facet_fields: List[str] = Query(
90+
default=None,
91+
title="Facet fields to include in the response",
92+
examples=["subject", "subject_taxon", "predicate"],
93+
),
94+
facet_queries: List[str] = Query(
95+
default=None, title="Facet queries to include in the response", examples=['subject_category:"biolink:Gene"']
96+
),
97+
filter_queries: List[str] = Query(
98+
default=None, title="Filter queries to limit the response", examples=['subject_category:"biolink:Gene"']
99+
),
89100
) -> Union[AssociationTableResults, str]:
90101
"""
91102
Retrieves association table data for a given entity and association type
@@ -105,6 +116,9 @@ def _association_table(
105116
q=query,
106117
traverse_orthologs=traverse_orthologs,
107118
direct=direct,
119+
facet_fields=facet_fields,
120+
facet_queries=facet_queries,
121+
filter_queries=filter_queries,
108122
sort=sort,
109123
offset=pagination.offset,
110124
limit=pagination.limit,

backend/src/monarch_py/datamodels/model.py

+112-2
Original file line numberDiff line numberDiff line change
@@ -2205,6 +2205,36 @@ class AssociationResults(Results):
22052205
}
22062206
},
22072207
)
2208+
facet_fields: Optional[List[FacetField]] = Field(
2209+
None,
2210+
description="""Collection of facet field responses with the field values and counts""",
2211+
json_schema_extra={
2212+
"linkml_meta": {
2213+
"alias": "facet_fields",
2214+
"domain_of": [
2215+
"AssociationResults",
2216+
"CompactAssociationResults",
2217+
"AssociationTableResults",
2218+
"SearchResults",
2219+
],
2220+
}
2221+
},
2222+
)
2223+
facet_queries: Optional[List[FacetValue]] = Field(
2224+
None,
2225+
description="""Collection of facet query responses with the query string values and counts""",
2226+
json_schema_extra={
2227+
"linkml_meta": {
2228+
"alias": "facet_queries",
2229+
"domain_of": [
2230+
"AssociationResults",
2231+
"CompactAssociationResults",
2232+
"AssociationTableResults",
2233+
"SearchResults",
2234+
],
2235+
}
2236+
},
2237+
)
22082238
limit: int = Field(
22092239
...,
22102240
description="""number of items to return in a response""",
@@ -2250,6 +2280,36 @@ class CompactAssociationResults(Results):
22502280
}
22512281
},
22522282
)
2283+
facet_fields: Optional[List[FacetField]] = Field(
2284+
None,
2285+
description="""Collection of facet field responses with the field values and counts""",
2286+
json_schema_extra={
2287+
"linkml_meta": {
2288+
"alias": "facet_fields",
2289+
"domain_of": [
2290+
"AssociationResults",
2291+
"CompactAssociationResults",
2292+
"AssociationTableResults",
2293+
"SearchResults",
2294+
],
2295+
}
2296+
},
2297+
)
2298+
facet_queries: Optional[List[FacetValue]] = Field(
2299+
None,
2300+
description="""Collection of facet query responses with the query string values and counts""",
2301+
json_schema_extra={
2302+
"linkml_meta": {
2303+
"alias": "facet_queries",
2304+
"domain_of": [
2305+
"AssociationResults",
2306+
"CompactAssociationResults",
2307+
"AssociationTableResults",
2308+
"SearchResults",
2309+
],
2310+
}
2311+
},
2312+
)
22532313
limit: int = Field(
22542314
...,
22552315
description="""number of items to return in a response""",
@@ -2295,6 +2355,36 @@ class AssociationTableResults(Results):
22952355
}
22962356
},
22972357
)
2358+
facet_fields: Optional[List[FacetField]] = Field(
2359+
None,
2360+
description="""Collection of facet field responses with the field values and counts""",
2361+
json_schema_extra={
2362+
"linkml_meta": {
2363+
"alias": "facet_fields",
2364+
"domain_of": [
2365+
"AssociationResults",
2366+
"CompactAssociationResults",
2367+
"AssociationTableResults",
2368+
"SearchResults",
2369+
],
2370+
}
2371+
},
2372+
)
2373+
facet_queries: Optional[List[FacetValue]] = Field(
2374+
None,
2375+
description="""Collection of facet query responses with the query string values and counts""",
2376+
json_schema_extra={
2377+
"linkml_meta": {
2378+
"alias": "facet_queries",
2379+
"domain_of": [
2380+
"AssociationResults",
2381+
"CompactAssociationResults",
2382+
"AssociationTableResults",
2383+
"SearchResults",
2384+
],
2385+
}
2386+
},
2387+
)
22982388
limit: int = Field(
22992389
...,
23002390
description="""number of items to return in a response""",
@@ -2673,12 +2763,32 @@ class SearchResults(Results):
26732763
facet_fields: Optional[List[FacetField]] = Field(
26742764
None,
26752765
description="""Collection of facet field responses with the field values and counts""",
2676-
json_schema_extra={"linkml_meta": {"alias": "facet_fields", "domain_of": ["SearchResults"]}},
2766+
json_schema_extra={
2767+
"linkml_meta": {
2768+
"alias": "facet_fields",
2769+
"domain_of": [
2770+
"AssociationResults",
2771+
"CompactAssociationResults",
2772+
"AssociationTableResults",
2773+
"SearchResults",
2774+
],
2775+
}
2776+
},
26772777
)
26782778
facet_queries: Optional[List[FacetValue]] = Field(
26792779
None,
26802780
description="""Collection of facet query responses with the query string values and counts""",
2681-
json_schema_extra={"linkml_meta": {"alias": "facet_queries", "domain_of": ["SearchResults"]}},
2781+
json_schema_extra={
2782+
"linkml_meta": {
2783+
"alias": "facet_queries",
2784+
"domain_of": [
2785+
"AssociationResults",
2786+
"CompactAssociationResults",
2787+
"AssociationTableResults",
2788+
"SearchResults",
2789+
],
2790+
}
2791+
},
26822792
)
26832793
limit: int = Field(
26842794
...,

backend/src/monarch_py/datamodels/model.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ classes:
125125
is_a: Results
126126
slots:
127127
- items
128+
- facet_fields
129+
- facet_queries
128130
slot_usage:
129131
items:
130132
range: Association
@@ -141,13 +143,17 @@ classes:
141143
is_a: Results
142144
slots:
143145
- items
146+
- facet_fields
147+
- facet_queries
144148
slot_usage:
145149
items:
146150
range: CompactAssociation
147151
AssociationTableResults:
148152
is_a: Results
149153
slots:
150154
- items
155+
- facet_fields
156+
- facet_queries
151157
slot_usage:
152158
items:
153159
range: DirectionalAssociation

backend/src/monarch_py/datamodels/solr.py

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class SolrQuery(BaseModel):
4444
facet_fields: Optional[List[str]] = Field(default_factory=list)
4545
facet_queries: Optional[List[str]] = Field(default_factory=list)
4646
filter_queries: Optional[List[str]] = Field(default_factory=list)
47+
facet_mincount: int = 1
4748
query_fields: Optional[str] = None
4849
def_type: str = "edismax"
4950
q_op: str = "AND" # See SOLR-8812, need this plus mm=100% to allow boolean operators in queries
@@ -85,6 +86,8 @@ def _solrize(self, value):
8586
return "facet.query"
8687
elif value == "filter_queries":
8788
return "fq"
89+
elif value == "facet_mincount":
90+
return "facet.mincount"
8891
elif value == "query_fields":
8992
return "qf"
9093
elif value == "def_type":

backend/src/monarch_py/implementations/solr/solr_implementation.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def get_entity(self, id: str, extra: bool) -> Optional[Union[Node, Entity]]:
123123
).items
124124
]
125125
node: Node = Node(
126-
**entity.dict(),
126+
**entity.model_dump(),
127127
node_hierarchy=self._get_node_hierarchy(entity),
128128
association_counts=self.get_association_counts(id).items,
129129
external_links=get_links_for_field(entity.xref) if entity.xref else [],
@@ -244,6 +244,9 @@ def get_associations(
244244
entity: Optional[List[str]] = None,
245245
direct: bool = False,
246246
q: Optional[str] = None,
247+
facet_fields: Optional[List[str]] = None,
248+
facet_queries: Optional[List[str]] = None,
249+
filter_queries: Optional[List[str]] = None,
247250
compact: bool = False,
248251
offset: int = 0,
249252
limit: int = 20,
@@ -259,6 +262,9 @@ def get_associations(
259262
object_closure: Filter to only associations with the specified term ID as an ancestor of the object. Defaults to None
260263
entity: Filter to only associations where the specified entities are the subject or the object. Defaults to None.
261264
q: Query string to search within matches. Defaults to None.
265+
facet_fields: List of fields to include facet counts for. Defaults to None.
266+
facet_queries: List of queries to include facet counts for. Defaults to None.
267+
filter_queries: List of queries to filter results by. Defaults to None.
262268
compact: Return compact results with fewer fields. Defaults to False.
263269
offset: Result offset, for pagination. Defaults to 0.
264270
limit: Limit results to specified number. Defaults to 20.
@@ -283,6 +289,9 @@ def get_associations(
283289
object_namespace=[object_namespace] if isinstance(object_namespace, str) else object_namespace,
284290
direct=direct,
285291
q=q,
292+
facet_fields=facet_fields,
293+
facet_queries=facet_queries,
294+
filter_queries=filter_queries,
286295
offset=offset,
287296
limit=limit,
288297
)
@@ -430,6 +439,7 @@ def get_association_facets(
430439
entity: Optional[List[str]] = None,
431440
facet_fields: Optional[List[str]] = None,
432441
facet_queries: Optional[List[str]] = None,
442+
filter_queries: Optional[List[str]] = None,
433443
) -> SearchResults:
434444
solr = SolrService(base_url=self.base_url, core=core.ASSOCIATION)
435445

@@ -445,6 +455,7 @@ def get_association_facets(
445455
limit=0,
446456
facet_fields=facet_fields,
447457
facet_queries=facet_queries,
458+
filter_queries=filter_queries,
448459
)
449460
query_result = solr.query(query)
450461
return SearchResults(
@@ -467,6 +478,9 @@ def get_association_table(
467478
traverse_orthologs: bool = False,
468479
direct: bool = False,
469480
q: Optional[str] = None,
481+
facet_fields: Optional[List[str]] = None,
482+
facet_queries: Optional[List[str]] = None,
483+
filter_queries: Optional[List[str]] = None,
470484
sort: Optional[List[str]] = None,
471485
offset: int = 0,
472486
limit: int = 5,
@@ -485,6 +499,9 @@ def get_association_table(
485499
category=category.value,
486500
direct=direct,
487501
q=q,
502+
facet_fields=facet_fields,
503+
facet_queries=facet_queries,
504+
filter_queries=filter_queries,
488505
sort=sort,
489506
offset=offset,
490507
limit=limit,

backend/src/monarch_py/implementations/solr/solr_parsers.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ def parse_associations(
5454
)
5555
for doc in query_result.response.docs
5656
]
57-
return CompactAssociationResults(items=associations, limit=limit, offset=offset, total=total)
57+
return CompactAssociationResults(
58+
items=associations,
59+
limit=limit,
60+
offset=offset,
61+
total=total,
62+
facet_fields=convert_facet_fields(query_result.facet_counts.facet_fields),
63+
facet_queries=convert_facet_queries(query_result.facet_counts.facet_queries),
64+
)
5865
else:
5966
for doc in query_result.response.docs:
6067
try:
@@ -72,7 +79,14 @@ def parse_associations(
7279
get_links_for_field(association.publications) if association.publications else []
7380
)
7481
associations.append(association)
75-
return AssociationResults(items=associations, limit=limit, offset=offset, total=total)
82+
return AssociationResults(
83+
items=associations,
84+
limit=limit,
85+
offset=offset,
86+
total=total,
87+
facet_fields=convert_facet_fields(query_result.facet_counts.facet_fields),
88+
facet_queries=convert_facet_queries(query_result.facet_counts.facet_queries),
89+
)
7690

7791

7892
def parse_association_counts(query_result: SolrQueryResult, entity: str) -> AssociationCountList:
@@ -140,7 +154,14 @@ def parse_association_table(
140154
except ValidationError:
141155
logger.error(f"Validation error for {doc}")
142156
raise
143-
results = AssociationTableResults(items=associations, limit=limit, offset=offset, total=total)
157+
results = AssociationTableResults(
158+
items=associations,
159+
limit=limit,
160+
offset=offset,
161+
total=total,
162+
facet_fields=convert_facet_fields(query_result.facet_counts.facet_fields),
163+
facet_queries=convert_facet_queries(query_result.facet_counts.facet_queries),
164+
)
144165
for i in zip(results.items, query_result.response.docs):
145166
assert i[0].subject == i[1]["subject"]
146167
return results

0 commit comments

Comments
 (0)