@@ -50,38 +50,40 @@ def get_by_gene_and_analysis(pairs):
5050 )
5151
5252 @staticmethod
53- def get_gene_strain_pairs_paginated (skip : int = 0 , limit : int = 10000 ):
53+ def get_gene_strain_pairs_paginated (after : str | None = None , limit : int = 10000 ):
5454 """
55- Return paginated (gene, genome_id) pairs directly from collection .
56- No $group - assumes data has no duplicates, InteropDB will upsert anyway .
55+ Return paginated (gene, genome_id) pairs using cursor-based pagination .
56+ Uses _id > after to seek directly, so every page is equally fast .
5757
5858 Args:
59- skip: Number of records to skip
59+ after: The _id of the last document from the previous page (None for first page)
6060 limit: Max records to return
6161
6262 Returns:
6363 {
6464 "pairs": [...],
65- "total ": int
65+ "next_cursor ": str | None (None means no more data)
6666 }
6767 """
68- # Get total count using aggregation
69- count_pipeline = [
70- {"$match" : {"gene" : {"$ne" : None }, "genome_id" : {"$ne" : None }}},
71- {"$count" : "total" }
72- ]
73- count_result = list (GeneInfo .objects .aggregate (count_pipeline ))
74- total = count_result [0 ]["total" ] if count_result else 0
68+ filter_query = {"gene" : {"$ne" : None }, "genome_id" : {"$ne" : None }, "locus_tag" : {"$ne" : None }}
69+ if after is not None :
70+ from bson import ObjectId
71+ filter_query ["_id" ] = {"$gt" : ObjectId (after )}
7572
76- # Direct find with skip/limit (fast)
7773 cursor = GeneInfo .objects .find (
78- {"gene" : {"$ne" : None }, "genome_id" : {"$ne" : None }, "locus_tag" : {"$ne" : None }},
79- projection = {"_id" : 0 , "gene" : 1 , "genome_id" : 1 , "locus_tag" : 1 }
80- ).skip (skip ).limit (limit )
74+ filter_query ,
75+ projection = {"_id" : 1 , "gene" : 1 , "genome_id" : 1 , "locus_tag" : 1 }
76+ ).sort ("_id" , 1 ).limit (limit )
77+
78+ pairs = []
79+ last_id = None
80+ for doc in cursor :
81+ pairs .append ({"gene" : doc ["gene" ], "strain" : doc ["genome_id" ], "locus_tag" : doc ["locus_tag" ]})
82+ last_id = str (doc ["_id" ])
8183
82- pairs = [{ "gene" : doc [ "gene" ], "strain" : doc [ "genome_id" ], "locus_tag" : doc [ "locus_tag" ]} for doc in cursor ]
84+ next_cursor = last_id if len ( pairs ) == limit else None
8385
84- return {"pairs" : pairs , "total " : total }
86+ return {"pairs" : pairs , "next_cursor " : next_cursor }
8587
8688 def get_gene_info_and_pangenomic_class_pipeline (gene_match ): # This is an ugly workaround to make it compatible with Azure Cosmos DB
8789 return [
@@ -177,16 +179,38 @@ def get_all_strains():
177179 return [doc ["genome_id" ] for doc in cursor if doc .get ("genome_id" )]
178180
179181 @staticmethod
180- def get_all_strains_paginated (skip = 0 , limit = 10000 ):
182+ def get_all_strains_paginated (after : str | None = None , limit : int = 10000 ):
181183 """
182- Return paginated distinct genome_id values.
183- Uses distinct() for fast retrieval, then slices in Python.
184+ Return paginated genome_id values using cursor-based pagination.
185+ Uses _id > after to seek directly, so every page is equally fast.
186+
187+ Args:
188+ after: The _id of the last document from the previous page (None for first page)
189+ limit: Max records to return
190+
191+ Returns:
192+ {"strains": [...], "next_cursor": str | None}
184193 """
185- col = GenomeInfo .objects .collection
186- all_ids = sorted (col .distinct ("genome_id" ))
187- total = len (all_ids )
188- page = all_ids [skip :skip + limit ]
189- return {"strains" : page , "total" : total }
194+ from bson import ObjectId
195+
196+ filter_query = {"genome_id" : {"$ne" : None }}
197+ if after is not None :
198+ filter_query ["_id" ] = {"$gt" : ObjectId (after )}
199+
200+ cursor = GenomeInfo .objects .find (
201+ filter_query ,
202+ projection = {"_id" : 1 , "genome_id" : 1 }
203+ ).sort ("_id" , 1 ).limit (limit )
204+
205+ strains = []
206+ last_id = None
207+ for doc in cursor :
208+ strains .append (doc ["genome_id" ])
209+ last_id = str (doc ["_id" ])
210+
211+ next_cursor = last_id if len (strains ) == limit else None
212+
213+ return {"strains" : strains , "next_cursor" : next_cursor }
190214
191215
192216 def get_genome_and_isolation_info_pipeline (genome_match ):
0 commit comments