Skip to content

Commit 6eba417

Browse files
authored
Merge pull request #13 from moleculemaker/feat/remove-500-limit
remove 500 search limit
2 parents 7c395aa + d87dfbc commit 6eba417

3 files changed

Lines changed: 100 additions & 70 deletions

File tree

app/db/queries.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,50 @@ async def build_conditions(
9393

9494
return where_clause, query_params
9595

96-
def get_query(columns_to_select: str, where_clause: str) -> str:
97-
return f"""
96+
SORTABLE_COLUMNS = {
97+
"accession": "pua.accession",
98+
"amino_acids": "pua.amino_acids",
99+
"organism": "pua.organism",
100+
"curation_status": "pua.curation_status",
101+
"predicted_ec": "puace.max_clean_ec_confidence",
102+
}
103+
104+
DEFAULT_ORDER_BY = "puace.max_clean_ec_confidence DESC, pua.amino_acids ASC, pua.predictions_uniprot_annot_id ASC"
105+
106+
107+
def parse_ordering(ordering: str | None) -> str:
108+
"""Parse an ordering string like '-accession' into a SQL ORDER BY clause.
109+
110+
Returns the default ordering if ordering is None or invalid.
111+
"""
112+
if not ordering:
113+
return DEFAULT_ORDER_BY
114+
115+
descending = ordering.startswith("-")
116+
field = ordering.lstrip("-")
117+
118+
if field not in SORTABLE_COLUMNS:
119+
return DEFAULT_ORDER_BY
120+
121+
direction = "DESC" if descending else "ASC"
122+
col = SORTABLE_COLUMNS[field]
123+
return f"{col} {direction}, pua.predictions_uniprot_annot_id ASC"
124+
125+
126+
def get_query(columns_to_select: str, where_clause: str, include_order_by: bool = True, ordering: str | None = None) -> str:
127+
query = f"""
98128
SELECT
99129
{columns_to_select}
100130
FROM cleandb.predictions_uniprot_annot pua
101131
INNER JOIN cleandb.predictions_uniprot_annot_clean_ec_mv01 puace
102132
ON puace.predictions_uniprot_annot_id = pua.predictions_uniprot_annot_id
103133
LEFT JOIN cleandb.predictions_uniprot_annot_ec_mv01 puae
104134
ON puae.predictions_uniprot_annot_id = pua.predictions_uniprot_annot_id
105-
WHERE {where_clause}
106-
ORDER BY puace.max_clean_ec_confidence DESC, pua.amino_acids ASC, pua.predictions_uniprot_annot_id ASC
107-
"""
135+
WHERE {where_clause}"""
136+
if include_order_by:
137+
query += f"""
138+
ORDER BY {parse_ordering(ordering)}"""
139+
return query
108140

109141
async def get_filtered_data(
110142
db: Database, params: CLEANSearchQueryParams
@@ -130,7 +162,7 @@ async def get_filtered_data(
130162
"""
131163

132164
# Build the main query
133-
query = get_query(columns_to_select, where_clause)
165+
query = get_query(columns_to_select, where_clause, ordering=params.ordering)
134166

135167
# Add pagination
136168
if params.limit is not None:
@@ -152,7 +184,7 @@ async def get_total_count(db: Database, params: CLEANSearchQueryParams) -> int:
152184
"""Get total count of records matching the filters."""
153185
where_clause, query_params = await build_conditions(params)
154186

155-
query = get_query("COUNT(*)", where_clause)
187+
query = get_query("COUNT(*)", where_clause, include_order_by=False)
156188

157189
# Extract query parameters from the dictionary
158190
query_args = list(query_params.values())

app/models/query_params.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ class CLEANSearchQueryParams(BaseModel):
6767
None, description="Maximum number of records to return"
6868
)
6969
offset: Optional[int] = Field(0, description="Number of records to skip")
70+
ordering: Optional[str] = Field(
71+
None,
72+
description="Column to sort by. Prefix with '-' for descending order. "
73+
"Allowed values: accession, amino_acids, organism, curation_status, predicted_ec",
74+
)
7075

7176
class CLEANTypeaheadQueryParams(BaseModel):
7277
"""Query parameters for CLEAN typeahead suggestions."""

app/routers/search.py

Lines changed: 56 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ def parse_query_params(
6464
None, description="Maximum number of records to return"
6565
),
6666
offset: Optional[int] = Query(0, description="Number of records to skip"),
67+
ordering: Optional[str] = Query(
68+
None,
69+
description="Column to sort by. Prefix with '-' for descending order. "
70+
"Allowed values: accession, amino_acids, organism, curation_status, predicted_ec",
71+
),
6772
) -> CLEANSearchQueryParams:
6873
"""Parse and validate query parameters."""
6974
try:
@@ -85,6 +90,7 @@ def parse_query_params(
8590
format=format,
8691
limit=limit,
8792
offset=offset,
93+
ordering=ordering,
8894
)
8995
except Exception as e:
9096
logger.error(f"Error parsing query parameters: {e}")
@@ -104,19 +110,14 @@ async def get_data(
104110
"""
105111

106112
try:
107-
# # Get total count for the query (without pagination)
108-
# total_count = await get_total_count(db, params)
109-
110-
# # Apply automatic pagination if results exceed threshold and no explicit limit provided
111-
# if total_count > settings.AUTO_PAGINATION_THRESHOLD and params.limit is None:
112-
# params.auto_paginated = True
113-
# params.limit = settings.AUTO_PAGINATION_THRESHOLD
114-
# logger.info(
115-
# f"Auto-pagination applied. Results limited to {params.limit} records."
116-
# )
117-
118-
params.limit = 500
119-
# Get data from database (now with potential auto-pagination applied)
113+
# Apply default page size if no explicit limit provided
114+
if params.limit is None:
115+
params.limit = settings.AUTO_PAGINATION_THRESHOLD
116+
117+
# Get total count for the query (without pagination)
118+
total_count = await get_total_count(db, params)
119+
120+
# Get data from database
120121
data = await get_filtered_data(db, params)
121122

122123
# Handle response format
@@ -148,12 +149,10 @@ async def get_data(
148149
headers={"Content-Disposition": "attachment; filename=CLEAN_data.csv"},
149150
)
150151
else:
151-
# TODO don't we want total_count to be the value returned by get_total_count?
152-
total_count = len(data)
153152
response = CLEANSearchResponse(
154153
total=total_count,
155154
offset=params.offset,
156-
limit=total_count if total_count < params.limit else params.limit,
155+
limit=params.limit,
157156
data=[CLEANDataBase(
158157
predictions_uniprot_annot_id=record["predictions_uniprot_annot_id"],
159158
uniprot=record["uniprot_id"],
@@ -177,54 +176,48 @@ async def get_data(
177176
) for record in data],
178177
)
179178

180-
# Add pagination links if automatic pagination was applied
181-
if params.auto_paginated:
182-
# Add flag indicating automatic pagination was applied
183-
response.auto_paginated = True
184-
185-
if request:
186-
base_url = str(request.url).split("?")[0]
187-
188-
# Prepare query parameters for pagination links
189-
# For Pydantic v2 compatibility
190-
query_params = {
191-
k: v
192-
for k, v in params.model_dump().items()
193-
if k not in ["auto_paginated", "offset", "limit"]
194-
and v is not None
179+
# Add pagination links
180+
if request:
181+
base_url = str(request.url).split("?")[0]
182+
183+
# Prepare query parameters for pagination links
184+
query_params = {
185+
k: v
186+
for k, v in params.model_dump().items()
187+
if k not in ["auto_paginated", "offset", "limit"]
188+
and v is not None
189+
}
190+
191+
# Set format explicitly if it was provided
192+
if params.format != ResponseFormat.JSON:
193+
query_params["format"] = params.format
194+
195+
current_offset = params.offset or 0
196+
current_limit = params.limit
197+
198+
# Next page link if there are more records
199+
if current_offset + current_limit < total_count:
200+
next_offset = current_offset + current_limit
201+
next_params = {
202+
**query_params,
203+
"offset": next_offset,
204+
"limit": current_limit,
195205
}
196-
197-
# Set format explicitly if it was provided
198-
if params.format != ResponseFormat.JSON:
199-
query_params["format"] = params.format
200-
201-
# Calculate next page link if there are more records
202-
current_offset = params.offset or 0
203-
current_limit = params.limit or total_count
204-
if current_offset + current_limit < total_count:
205-
next_offset = current_offset + current_limit
206-
next_params = {
207-
**query_params,
208-
"offset": next_offset,
209-
"limit": current_limit,
210-
}
211-
response.next = (
212-
f"{base_url}?{urlencode(next_params, doseq=True)}"
213-
)
214-
215-
# Calculate previous page link if not on first page
216-
current_offset = params.offset or 0
217-
current_limit = params.limit or total_count
218-
if current_offset > 0:
219-
prev_offset = max(0, current_offset - current_limit)
220-
prev_params = {
221-
**query_params,
222-
"offset": prev_offset,
223-
"limit": current_limit,
224-
}
225-
response.previous = (
226-
f"{base_url}?{urlencode(prev_params, doseq=True)}"
227-
)
206+
response.next = (
207+
f"{base_url}?{urlencode(next_params, doseq=True)}"
208+
)
209+
210+
# Previous page link if not on first page
211+
if current_offset > 0:
212+
prev_offset = max(0, current_offset - current_limit)
213+
prev_params = {
214+
**query_params,
215+
"offset": prev_offset,
216+
"limit": current_limit,
217+
}
218+
response.previous = (
219+
f"{base_url}?{urlencode(prev_params, doseq=True)}"
220+
)
228221

229222
return response
230223

0 commit comments

Comments
 (0)