-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add OpenAPI /swagger-ui/index.html and /api-docs (#582)
* Add OpenAPI docs to exomiser-rest-prioritiser Alter context path to `/exomiser-prioritiser` Add `swagger-ui` and `api-docs` endpoints Update PrioritiserController @RequestMapping to `api/v1` with two `prioritise` end points (GET and POST) Extract new PrioritiserService from PrioritiserController for easier testing. * Add @OpenAPIDefinition to ExomiserPrioritiserServer Update PrioritiserController endpoints from `/api/v1/prioritise` to `/api/v1/prioritise/gene` Update PrioritiserService.prioritise() method to prioritiseGenes Set `hiphive` as the default prioritiser and set requirement to not required. Add a README.md
- Loading branch information
1 parent
55ca701
commit d8b8eff
Showing
11 changed files
with
603 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
Exomiser Prioritiser REST API | ||
=== | ||
|
||
Requirements | ||
-- | ||
The jar file built from this maven module, or pre-built versions can be found on GitHub e.g. | ||
https://github.com/exomiser/Exomiser/releases/download/14.1.0/exomiser-rest-prioritiser-14.1.0.jar | ||
|
||
And a current version of the phenotype data. The data is updated a few times a year and the release announcements are | ||
also on GitHub: https://github.com/exomiser/Exomiser/discussions/categories/data-release | ||
|
||
In this example we're using the 2410_phenotype data release which can be found here: | ||
https://g-879a9f.f5dc97.75bc.dn.glob.us/data/2410_phenotype.zip | ||
|
||
|
||
Setup | ||
-- | ||
This is a Spring Boot application, which means it can probably be configured to run the way you need for your setup. It | ||
will require configuration either using a properties file, which you can find in | ||
`src/main/resources/application.properties`. Alternatively, these can be provided as command-line arguments or environment | ||
variables when launching the application. More info on configuration of Spring Boot applications can be found in their | ||
[docs](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files) | ||
|
||
To set up on your local machine: | ||
- Create a new directory called `exomiser` and download the jar, application.properties and zip files into this. Extract | ||
the zip file so that you have should now have a `2410_phenotype` subfolder. | ||
|
||
- Edit the application.properties so that it looks like this: | ||
``` | ||
exomiser.data-directory=full/path/to/your/new/exomiser/dir | ||
exomiser.phenotype.data-version=2410 | ||
``` | ||
You might need to delete the keys starting `info` - they will be present in the app, and you shouldn't need to change them. | ||
|
||
- In a terminal, launch the app using `java -jar exomiser-rest-prioritiser-14.1.0.jar` from the `exomiser` folder. You | ||
should see a bunch of logging output which stops after a few seconds with these lines: | ||
``` | ||
2024-12-18T10:23:33.827Z INFO 452968 --- [exomiser-prioritiser-service] [ main] o.m.e.r.p.api.PrioritiserController : Started PrioritiserController with GeneIdentifier cache of 19762 entries | ||
2024-12-18T10:23:34.249Z INFO 452968 --- [exomiser-prioritiser-service] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' | ||
2024-12-18T10:23:34.305Z INFO 452968 --- [exomiser-prioritiser-service] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8085 (http) with context path '/exomiser-prioritiser' | ||
2024-12-18T10:23:34.322Z INFO 452968 --- [exomiser-prioritiser-service] [ main] o.m.e.r.p.ExomiserPrioritiserServer : Started ExomiserPrioritiserServer in 6.056 seconds (process running for 6.676) | ||
``` | ||
It is now ready to use. Where you keep the jar and the data files is up to you. You just need to tell the application | ||
where the data can be found using the full path to the parent directory where the data has been unpacked in the | ||
application.properties. For example, if you unpacked the data to `/data/2410_phenotype` then`exomiser.data-directory=/data` | ||
and `exomiser.phenotype.data-version=2410`. | ||
Note that if you have an existing Exomiser CLI installation, you can add this jar file to that directory and the REST | ||
service will use the properties from the existing `application.properties`. Alternatively the `exomiser.data-directory` | ||
and `exomiser.phenotype.data-version` can be supplied as command-line arguments when starting the jar without the need | ||
for an `application.properties` file. | ||
Running | ||
--- | ||
There is an OpenAPI 3 page which should be accessible here if everything went successfully: | ||
```shell | ||
http://localhost:8085/exomiser-prioritiser/swagger-ui/index.html | ||
``` | ||
|
||
This contains examples of the input parameters and the expected output. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,63 +20,99 @@ | |
|
||
package org.monarchinitiative.exomiser.rest.prioritiser.api; | ||
|
||
import org.monarchinitiative.exomiser.core.model.Gene; | ||
import org.monarchinitiative.exomiser.core.model.GeneIdentifier; | ||
import org.monarchinitiative.exomiser.core.prioritisers.HiPhiveOptions; | ||
import org.monarchinitiative.exomiser.core.prioritisers.Prioritiser; | ||
import org.monarchinitiative.exomiser.core.prioritisers.PriorityFactory; | ||
import org.monarchinitiative.exomiser.core.prioritisers.PriorityResult; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.Parameter; | ||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import org.monarchinitiative.exomiser.rest.prioritiser.service.PrioritiserService; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.core.io.ClassPathResource; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.io.IOException; | ||
import java.time.Duration; | ||
import java.time.Instant; | ||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import java.util.Set; | ||
|
||
/** | ||
* @author Jules Jacobsen <[email protected]> | ||
*/ | ||
@RestController | ||
@RequestMapping("api/v1/prioritise") | ||
@Tag(name = "Prioritiser", description = "API endpoints for phenotype-based gene prioritisation") | ||
public class PrioritiserController { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(PrioritiserController.class); | ||
|
||
private final Map<Integer, GeneIdentifier> geneIdentifiers; | ||
private final PriorityFactory priorityFactory; | ||
private final PrioritiserService prioritiserService; | ||
|
||
@Autowired | ||
public PrioritiserController(Map<Integer, GeneIdentifier> geneIdentifiers, PriorityFactory priorityFactory) { | ||
this.geneIdentifiers = geneIdentifiers; | ||
this.priorityFactory = priorityFactory; | ||
logger.info("Started PrioritiserController with GeneIdentifier cache of {} entries", geneIdentifiers.size()); | ||
} | ||
|
||
@GetMapping(value = "/about") | ||
public String about() { | ||
byte[] bytes = new byte[0]; | ||
try { | ||
bytes = new ClassPathResource("about.html").getInputStream().readAllBytes(); | ||
} catch (IOException e) { | ||
logger.error("", e); | ||
} | ||
return new String(bytes); | ||
public PrioritiserController(PrioritiserService prioritiserService) { | ||
this.prioritiserService = prioritiserService; | ||
} | ||
|
||
@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE) | ||
public PrioritiserResultSet prioritise(@RequestParam(value = "phenotypes") Set<String> phenotypes, | ||
@RequestParam(value = "genes", required = false, defaultValue = "") Set<Integer> genesIds, | ||
@RequestParam(value = "prioritiser") String prioritiserName, | ||
@RequestParam(value = "prioritiser-params", required = false, defaultValue = "") String prioritiserParams, | ||
@RequestParam(value = "limit", required = false, defaultValue = "0") Integer limit | ||
@Operation( | ||
summary = "Prioritise genes by phenotype", | ||
description = "Prioritises genes based on provided phenotypes and other parameters" | ||
) | ||
@ApiResponses(value = { | ||
@ApiResponse( | ||
responseCode = "200", | ||
description = "Successfully prioritised genes", | ||
content = @Content( | ||
mediaType = MediaType.APPLICATION_JSON_VALUE, | ||
schema = @Schema(implementation = PrioritiserResultSet.class) | ||
) | ||
), | ||
@ApiResponse( | ||
responseCode = "400", | ||
description = "Invalid input parameters" | ||
) | ||
}) | ||
@GetMapping(value = "gene", produces = MediaType.APPLICATION_JSON_VALUE) | ||
public PrioritiserResultSet prioritiseGenes( | ||
@Parameter( | ||
description = "Set of HPO phenotype identifiers", | ||
example = "[\"HP:0001156\", \"HP:0001363\", \"HP:0011304\", \"HP:0010055\"]", | ||
required = true | ||
) | ||
@RequestParam(value = "phenotypes") Set<String> phenotypes, | ||
|
||
@Parameter( | ||
description = "Set of NCBI gene IDs to consider in prioritisation", | ||
example = "[2263, 2264]", | ||
required = false | ||
) | ||
@RequestParam(value = "genes", required = false, defaultValue = "") Set<Integer> genesIds, | ||
|
||
@Parameter( | ||
description = "Name of the prioritiser algorithm to use. One of ['hiphive', 'phenix', 'phive']. " + | ||
"Defaults to 'hiphive' which allows for cross-species and PPI hits. 'phenix' is a" + | ||
" legacy prioritiser which will only prioritise human disease-gene associations. It is" + | ||
" the equivalent of 'hiphive' with prioritiser-params='human'. 'phive' is just the" + | ||
" mouse subset of hiphive, equivalent to 'hiphive' with prioritiser-params='mouse'.", | ||
example = "hiphive", | ||
required = false | ||
) | ||
@RequestParam(value = "prioritiser", defaultValue = "hiphive") String prioritiserName, | ||
|
||
@Parameter( | ||
description = "Additional parameters for the prioritiser. This is optional for the 'hiphive' prioritiser." + | ||
" values can be at least one of 'human,mouse,fish,ppi'. Will default to all, however" + | ||
" just 'human' will restrict matches to known human disease-gene associations.", | ||
example = "human", | ||
required = false | ||
) | ||
@RequestParam(value = "prioritiser-params", required = false, defaultValue = "") String prioritiserParams, | ||
|
||
@Parameter( | ||
description = "Maximum number of results to return (0 for unlimited)", | ||
required = false, | ||
example = "20" | ||
) | ||
@RequestParam(value = "limit", required = false, defaultValue = "0") Integer limit | ||
) { | ||
PrioritiserRequest prioritiserRequest = PrioritiserRequest.builder() | ||
.prioritiser(prioritiserName) | ||
|
@@ -86,79 +122,40 @@ public PrioritiserResultSet prioritise(@RequestParam(value = "phenotypes") Set<S | |
.limit(limit) | ||
.build(); | ||
|
||
return prioritise(prioritiserRequest); | ||
return prioritiseGenes(prioritiserRequest); | ||
} | ||
|
||
@PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) | ||
public PrioritiserResultSet prioritise(@RequestBody PrioritiserRequest prioritiserRequest) { | ||
logger.info("{}", prioritiserRequest); | ||
|
||
Instant start = Instant.now(); | ||
|
||
Prioritiser<? extends PriorityResult> prioritiser = parsePrioritiser(prioritiserRequest.getPrioritiser(), prioritiserRequest | ||
.getPrioritiserParams()); | ||
List<Gene> genes = makeGenesFromIdentifiers(prioritiserRequest.getGenes()); | ||
|
||
List<PriorityResult> results = runLimitAndCollectResults(prioritiser, prioritiserRequest.getPhenotypes(), genes, prioritiserRequest | ||
.getLimit()); | ||
|
||
Instant end = Instant.now(); | ||
Duration duration = Duration.between(start, end); | ||
|
||
return new PrioritiserResultSet(prioritiserRequest, duration.toMillis(), results); | ||
} | ||
|
||
private Prioritiser<? extends PriorityResult> parsePrioritiser(String prioritiserName, String prioritiserParams) { | ||
switch (prioritiserName) { | ||
case "phenix": | ||
return priorityFactory.makePhenixPrioritiser(); | ||
case "phive": | ||
return priorityFactory.makePhivePrioritiser(); | ||
case "hiphive": | ||
default: | ||
HiPhiveOptions hiPhiveOptions = HiPhiveOptions.builder() | ||
.runParams(prioritiserParams) | ||
.build(); | ||
return priorityFactory.makeHiPhivePrioritiser(hiPhiveOptions); | ||
} | ||
} | ||
|
||
private List<Gene> makeGenesFromIdentifiers(Collection<Integer> genesIds) { | ||
if (genesIds.isEmpty()) { | ||
logger.info("Gene identifiers not specified - will compare against all known genes."); | ||
//If not specified, we'll assume they want to use the whole genome. Should save people a lot of typing. | ||
//n.b. Gene is mutable so these can't be cached and returned. | ||
return allGenes(); | ||
} | ||
// This is a hack - really the Prioritiser should only work on GeneIds, but currently this isn't possible as | ||
// OmimPrioritiser uses some properties of Gene | ||
return genesIds.stream() | ||
.map(id -> new Gene(geneIdentifiers.getOrDefault(id, unrecognisedGeneIdentifier(id)))) | ||
.collect(toImmutableList()); | ||
} | ||
|
||
private List<Gene> allGenes() { | ||
return geneIdentifiers.values().parallelStream() | ||
.map(Gene::new) | ||
.collect(toImmutableList()); | ||
} | ||
|
||
private GeneIdentifier unrecognisedGeneIdentifier(Integer id) { | ||
return GeneIdentifier.builder().geneSymbol("GENE:" + id).build(); | ||
} | ||
|
||
private <T extends PriorityResult> List<PriorityResult> runLimitAndCollectResults(Prioritiser<T> prioritiser, List<String> phenotypes, List<Gene> genes, int limit) { | ||
Set<Integer> wantedGeneIds = genes.stream().map(Gene::getEntrezGeneID).collect(Collectors.toSet()); | ||
|
||
Stream<T> resultsStream = prioritiser.prioritise(phenotypes, genes) | ||
.filter(result -> wantedGeneIds.contains(result.getGeneId())) | ||
.sorted(Comparator.naturalOrder()); | ||
|
||
logger.info("Finished {}", prioritiser.getPriorityType()); | ||
if (limit == 0) { | ||
return resultsStream.collect(toImmutableList()); | ||
} | ||
return resultsStream.limit(limit).collect(toImmutableList()); | ||
@Operation( | ||
summary = "Prioritise genes using POST request", | ||
description = "Prioritises genes based on provided request body containing phenotypes and configuration" | ||
) | ||
@ApiResponses(value = { | ||
@ApiResponse( | ||
responseCode = "200", | ||
description = "Successfully prioritised genes", | ||
content = @Content( | ||
mediaType = MediaType.APPLICATION_JSON_VALUE, | ||
schema = @Schema(implementation = PrioritiserResultSet.class) | ||
) | ||
), | ||
@ApiResponse( | ||
responseCode = "400", | ||
description = "Invalid request body" | ||
) | ||
}) | ||
@PostMapping( | ||
value = "gene", | ||
consumes = MediaType.APPLICATION_JSON_VALUE, | ||
produces = MediaType.APPLICATION_JSON_VALUE | ||
) | ||
public PrioritiserResultSet prioritiseGenes( | ||
@Parameter( | ||
description = "Prioritisation request parameters", | ||
required = true | ||
) | ||
@RequestBody PrioritiserRequest prioritiserRequest | ||
) { | ||
return prioritiserService.prioritiseGenes(prioritiserRequest); | ||
} | ||
|
||
} |
Oops, something went wrong.