1818 summary = "List all genes (paginated)" ,
1919 description = (
2020 "Return distinct (gene, species) pairs with PanKB URLs. "
21- "Supports cursor-based pagination via skip /limit query parameters."
21+ "Supports cursor-based pagination via after /limit query parameters."
2222 ),
2323 parameters = [
24- OpenApiParameter (name = "skip " , type = int , location = "query" , description = "Number of records to skip (default 0 )" ),
25- OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 10000 , max 50000 )" ),
24+ OpenApiParameter (name = "after " , type = str , location = "query" , description = "Cursor from previous page (next_cursor value )" ),
25+ OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 50000 , max 200000 )" ),
2626 ],
2727 responses = {
2828 200 : {
29+ "description" : "Paginated list of genes with PanKB URLs." ,
2930 "type" : "object" ,
3031 "properties" : {
3132 "genes" : {
3940 },
4041 },
4142 },
42- "total" : {"type" : "integer" },
43- "skip" : {"type" : "integer" },
4443 "limit" : {"type" : "integer" },
44+ "next_cursor" : {"type" : "string" , "nullable" : True },
4545 "has_more" : {"type" : "boolean" },
4646 },
47- }
47+ },
48+ 500 : {"description" : "Unexpected server error." },
4849 },
4950)
5051@api_view (["GET" ])
@@ -81,14 +82,15 @@ def genes(request):
8182 summary = "List all strains (paginated)" ,
8283 description = (
8384 "Return all strains (genome IDs) with PanKB URLs. "
84- "Supports cursor-based pagination via skip /limit query parameters."
85+ "Supports cursor-based pagination via after /limit query parameters."
8586 ),
8687 parameters = [
87- OpenApiParameter (name = "skip " , type = int , location = "query" , description = "Number of records to skip (default 0 )" ),
88- OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 10000 , max 50000 )" ),
88+ OpenApiParameter (name = "after " , type = str , location = "query" , description = "Cursor from previous page (next_cursor value )" ),
89+ OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 50000 , max 200000 )" ),
8990 ],
9091 responses = {
9192 200 : {
93+ "description" : "Paginated list of strains with PanKB URLs." ,
9294 "type" : "object" ,
9395 "properties" : {
9496 "strains" : {
@@ -101,12 +103,12 @@ def genes(request):
101103 },
102104 },
103105 },
104- "total" : {"type" : "integer" },
105- "skip" : {"type" : "integer" },
106106 "limit" : {"type" : "integer" },
107+ "next_cursor" : {"type" : "string" , "nullable" : True },
107108 "has_more" : {"type" : "boolean" },
108109 },
109- }
110+ },
111+ 500 : {"description" : "Unexpected server error." },
110112 },
111113)
112114@api_view (["GET" ])
@@ -142,14 +144,15 @@ def strains(request):
142144 summary = "List gene-strain pairs (paginated)" ,
143145 description = (
144146 "Return distinct (gene, strain, locus_tag) pairs with PanKB URLs. "
145- "Supports cursor-based pagination via skip /limit query parameters."
147+ "Supports cursor-based pagination via after /limit query parameters."
146148 ),
147149 parameters = [
148- OpenApiParameter (name = "skip " , type = int , location = "query" , description = "Number of records to skip (default 0 )" ),
149- OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 10000 , max 50000 )" ),
150+ OpenApiParameter (name = "after " , type = str , location = "query" , description = "Cursor from previous page (next_cursor value )" ),
151+ OpenApiParameter (name = "limit" , type = int , location = "query" , description = "Max records to return (default 50000 , max 200000 )" ),
150152 ],
151153 responses = {
152154 200 : {
155+ "description" : "Paginated list of gene-strain pairs with PanKB URLs." ,
153156 "type" : "object" ,
154157 "properties" : {
155158 "pairs" : {
@@ -164,12 +167,12 @@ def strains(request):
164167 },
165168 },
166169 },
167- "total" : {"type" : "integer" },
168- "skip" : {"type" : "integer" },
169170 "limit" : {"type" : "integer" },
171+ "next_cursor" : {"type" : "string" , "nullable" : True },
170172 "has_more" : {"type" : "boolean" },
171173 },
172- }
174+ },
175+ 500 : {"description" : "Unexpected server error." },
173176 },
174177)
175178@api_view (["GET" ])
@@ -203,7 +206,13 @@ def gene_strain_pairs(request):
203206@extend_schema (
204207 tags = ["Gene-Strain Pairs" ],
205208 summary = "Query by gene-strain pairs" ,
206- description = "Look up detailed gene info for specific gene/strain pair combinations." ,
209+ description = (
210+ "Look up detailed gene info for specific gene/strain pair combinations. "
211+ "Accepts JSON body: {\" pairs\" : [{\" gene\" : \" ...\" , \" strain\" : \" ...\" }]}. "
212+ "Both 'gene' and 'strain' must be strings (not arrays). Each pair represents one gene-strain combination. "
213+ "Returns 400 if the pairs list is empty or entries are missing required fields. "
214+ "IDs not found in the database are silently omitted from the response."
215+ ),
207216 request = {
208217 "application/json" : {
209218 "type" : "object" ,
@@ -223,7 +232,30 @@ def gene_strain_pairs(request):
223232 "required" : ["pairs" ],
224233 }
225234 },
226- responses = {200 : {"type" : "array" , "items" : {"type" : "object" }}},
235+ responses = {
236+ 200 : {
237+ "description" : "Gene info for matched gene/strain pairs." ,
238+ "type" : "array" ,
239+ "items" : {
240+ "type" : "object" ,
241+ "properties" : {
242+ "gene" : {"type" : "string" },
243+ "locus_tag" : {"type" : "string" },
244+ "genome_id" : {"type" : "string" },
245+ "protein" : {"type" : "string" },
246+ "species" : {"type" : "string" },
247+ "pangenome_analysis" : {"type" : "string" },
248+ "start_position" : {"type" : "integer" },
249+ "end_position" : {"type" : "integer" },
250+ "nucleotide_seq" : {"type" : "string" },
251+ "aminoacid_seq" : {"type" : "string" },
252+ "url" : {"type" : "string" , "format" : "uri" },
253+ },
254+ },
255+ },
256+ 400 : {"description" : "Invalid or empty input." },
257+ 500 : {"description" : "Unexpected server error." },
258+ },
227259 examples = [
228260 OpenApiExample (
229261 "Example request" ,
@@ -242,20 +274,23 @@ def query_by_pair(request):
242274 pairs = [pairs ]
243275
244276 if not pairs :
245- return Response ({
246- "count " : 0 ,
247- "message" : "No gene/strain pairs provided" ,
248- } )
277+ return Response (
278+ { "message " : "pairs must be a non-empty list" } ,
279+ status = status . HTTP_400_BAD_REQUEST ,
280+ )
249281
250- clean_pairs = [
251- {"gene" : p ["gene" ], "genome_id" : p ["strain" ]}
252- for p in pairs
253- if "gene" in p and "strain" in p
254- ]
282+ seen = set ()
283+ clean_pairs = []
284+ for p in pairs :
285+ if "gene" in p and "strain" in p :
286+ key = (p ["gene" ], p ["strain" ])
287+ if key not in seen :
288+ seen .add (key )
289+ clean_pairs .append ({"gene" : p ["gene" ], "genome_id" : p ["strain" ]})
255290 if not clean_pairs :
256- return Response ({
257- "count " : 0 ,
258- "message" : "Each pair must contain both 'gene' and 'strain'" ,
291+ return Response (
292+ { "message " : "Each pair must contain both 'gene' and 'strain'" } ,
293+ status = status . HTTP_400_BAD_REQUEST ,
259294 })
260295
261296 genes = {p ["gene" ] for p in clean_pairs }
@@ -299,7 +334,12 @@ def query_by_pair(request):
299334@extend_schema (
300335 tags = ["Genes" ],
301336 summary = "Query by gene names" ,
302- description = "Look up detailed gene info by a list of gene names." ,
337+ description = (
338+ "Look up detailed gene info by a list of gene names. "
339+ "Accepts JSON body: {\" ids\" : [\" geneA\" , \" geneB\" ]}. "
340+ "Returns 400 if the ids list is empty or missing. "
341+ "IDs not found in the database are silently omitted from the response."
342+ ),
303343 request = {
304344 "application/json" : {
305345 "type" : "object" ,
@@ -312,7 +352,30 @@ def query_by_pair(request):
312352 "required" : ["ids" ],
313353 }
314354 },
315- responses = {200 : {"type" : "array" , "items" : {"type" : "object" }}},
355+ responses = {
356+ 200 : {
357+ "description" : "Gene info for matched gene names." ,
358+ "type" : "array" ,
359+ "items" : {
360+ "type" : "object" ,
361+ "properties" : {
362+ "gene" : {"type" : "string" },
363+ "locus_tag" : {"type" : "string" },
364+ "genome_id" : {"type" : "string" },
365+ "protein" : {"type" : "string" },
366+ "species" : {"type" : "string" },
367+ "pangenome_analysis" : {"type" : "string" },
368+ "start_position" : {"type" : "integer" },
369+ "end_position" : {"type" : "integer" },
370+ "nucleotide_seq" : {"type" : "string" },
371+ "aminoacid_seq" : {"type" : "string" },
372+ "url" : {"type" : "string" , "format" : "uri" },
373+ },
374+ },
375+ },
376+ 400 : {"description" : "Invalid or empty input." },
377+ 500 : {"description" : "Unexpected server error." },
378+ },
316379 examples = [
317380 OpenApiExample (
318381 "Example request" ,
@@ -355,7 +418,12 @@ def query_by_gene(request):
355418@extend_schema (
356419 tags = ["Strains" ],
357420 summary = "Query by strain IDs" ,
358- description = "Look up genome info by a list of genome IDs." ,
421+ description = (
422+ "Look up genome info by a list of genome IDs. "
423+ "Accepts JSON body: {\" ids\" : [\" GCF_...\" , \" GCF_...\" ]}. "
424+ "Returns 400 if the ids list is empty or missing. "
425+ "IDs not found in the database are silently omitted from the response."
426+ ),
359427 request = {
360428 "application/json" : {
361429 "type" : "object" ,
@@ -368,7 +436,31 @@ def query_by_gene(request):
368436 "required" : ["ids" ],
369437 }
370438 },
371- responses = {200 : {"type" : "array" , "items" : {"type" : "object" }}},
439+ responses = {
440+ 200 : {
441+ "description" : "Genome info for matched strain IDs." ,
442+ "type" : "array" ,
443+ "items" : {
444+ "type" : "object" ,
445+ "properties" : {
446+ "genome_id" : {"type" : "string" },
447+ "strain" : {"type" : "string" },
448+ "species" : {"type" : "string" },
449+ "pangenome_analysis" : {"type" : "string" },
450+ "gc_content" : {"type" : "number" },
451+ "genome_len" : {"type" : "integer" },
452+ "gene_class_distribution" : {"type" : "array" , "items" : {"type" : "integer" }},
453+ "phylo_group" : {"type" : "string" },
454+ "isolation_source" : {"type" : "string" },
455+ "country" : {"type" : "string" },
456+ "geo_loc_name" : {"type" : "string" },
457+ "url" : {"type" : "string" , "format" : "uri" },
458+ },
459+ },
460+ },
461+ 400 : {"description" : "Invalid or empty input." },
462+ 500 : {"description" : "Unexpected server error." },
463+ },
372464 examples = [
373465 OpenApiExample (
374466 "Example request" ,
0 commit comments