11import csv
22from io import StringIO
33from typing import Any , List , Optional
4+ from urllib .parse import urlencode
45
5- from fastapi import APIRouter , Depends , HTTPException , Query
6+ from fastapi import APIRouter , Depends , HTTPException , Query , Request
67from fastapi .responses import StreamingResponse
78from loguru import logger
89
10+ from app .core .config import settings
911from app .db .database import Database , get_db
1012from app .db .queries import get_filtered_data , get_total_count
1113from app .models .query_params import OEDDataQueryParams , ResponseFormat
@@ -79,6 +81,7 @@ def parse_query_params(
7981async def get_data (
8082 params : OEDDataQueryParams = Depends (parse_query_params ),
8183 db : Database = Depends (get_db ),
84+ request : Request = None ,
8285) -> Any :
8386 """
8487 Get enzyme kinetic data with filtering options.
@@ -90,6 +93,10 @@ async def get_data(
9093
9194 The response format can be either JSON (default) or CSV.
9295
96+ Results are automatically paginated when they exceed the configured threshold
97+ (default: configurable via AUTO_PAGINATION_THRESHOLD in config.Settings), unless
98+ an explicit limit is provided.
99+
93100 Example queries:
94101
95102 - /api/v1/data?organism=Homo%20sapiens&organism=Mus%20musculus
@@ -100,12 +107,20 @@ async def get_data(
100107 """
101108
102109 try :
103- # Get data from database
104- data = await get_filtered_data (db , params )
105-
106110 # Get total count for the query (without pagination)
107111 total_count = await get_total_count (db , params )
108112
113+ # Apply automatic pagination if results exceed threshold and no explicit limit provided
114+ if total_count > settings .AUTO_PAGINATION_THRESHOLD and params .limit is None :
115+ params .auto_paginated = True
116+ params .limit = settings .AUTO_PAGINATION_THRESHOLD
117+ logger .info (
118+ f"Auto-pagination applied. Results limited to { params .limit } records."
119+ )
120+
121+ # Get data from database (now with potential auto-pagination applied)
122+ data = await get_filtered_data (db , params )
123+
109124 # Handle response format
110125 if params .format == ResponseFormat .CSV :
111126 # Create CSV response
@@ -135,14 +150,65 @@ async def get_data(
135150 headers = {"Content-Disposition" : "attachment; filename=oed_data.csv" },
136151 )
137152 else :
138- # JSON response
139- return {
153+ # JSON response with enhanced pagination info
154+ response = {
140155 "total" : total_count ,
141156 "offset" : params .offset ,
142157 "limit" : params .limit if params .limit is not None else total_count ,
143158 "data" : data ,
144159 }
145160
161+ # Add pagination links if automatic pagination was applied
162+ if params .auto_paginated :
163+ # Add flag indicating automatic pagination was applied
164+ response ["auto_paginated" ] = True
165+
166+ if request :
167+ base_url = str (request .url ).split ("?" )[0 ]
168+
169+ # Prepare query parameters for pagination links
170+ # For Pydantic v2 compatibility
171+ query_params = {
172+ k : v
173+ for k , v in params .model_dump ().items ()
174+ if k not in ["auto_paginated" , "offset" , "limit" ]
175+ and v is not None
176+ }
177+
178+ # Set format explicitly if it was provided
179+ if params .format != ResponseFormat .JSON :
180+ query_params ["format" ] = params .format
181+
182+ # Calculate next page link if there are more records
183+ current_offset = params .offset or 0
184+ current_limit = params .limit or total_count
185+ if current_offset + current_limit < total_count :
186+ next_offset = current_offset + current_limit
187+ next_params = {
188+ ** query_params ,
189+ "offset" : next_offset ,
190+ "limit" : current_limit ,
191+ }
192+ response ["next" ] = (
193+ f"{ base_url } ?{ urlencode (next_params , doseq = True )} "
194+ )
195+
196+ # Calculate previous page link if not on first page
197+ current_offset = params .offset or 0
198+ current_limit = params .limit or total_count
199+ if current_offset > 0 :
200+ prev_offset = max (0 , current_offset - current_limit )
201+ prev_params = {
202+ ** query_params ,
203+ "offset" : prev_offset ,
204+ "limit" : current_limit ,
205+ }
206+ response ["previous" ] = (
207+ f"{ base_url } ?{ urlencode (prev_params , doseq = True )} "
208+ )
209+
210+ return response
211+
146212 except Exception as e :
147213 logger .error (f"Error getting data: { e } " )
148214 raise HTTPException (status_code = 500 , detail = f"Error retrieving data: { str (e )} " )
0 commit comments