Skip to content

Commit 96db477

Browse files
feat(lapis): expose the lineage definition files used by SILO
resolves #1034
1 parent 0f07555 commit 96db477

File tree

9 files changed

+132
-4
lines changed

9 files changed

+132
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Higher versions will also work if they are not specified in the table.
5151

5252
| LAPIS | SILO |
5353
|--------|--------|
54+
| 0.3.14 | 0.5.3 |
5455
| 0.3.13 | 0.5.2 |
5556
| 0.3.7 | 0.3.0 |
5657
| 0.2.10 | 0.2.14 |

lapis-e2e/test/info.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { expect } from 'chai';
22
import { lapisInfoClient } from './common';
33

4-
describe('The info endpoind', () => {
4+
describe('The info endpoint', () => {
55
it('should return all infos', async () => {
66
const info = await lapisInfoClient.getInfo();
77

8-
console.log(info);
9-
108
expect(info.dataVersion).to.match(/\d+/);
119
expect(info.lapisVersion).to.be.not.empty;
1210
expect(info.siloVersion).to.be.not.empty;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { expect } from 'chai';
2+
import { basePath, lapisInfoClient } from './common';
3+
4+
describe('The lineageDefinition endpoint', () => {
5+
it('should return the file as JSON', async () => {
6+
const lineageDefinition = await lapisInfoClient.getLineageDefinition({
7+
column: 'pango_lineage',
8+
});
9+
10+
expect(lineageDefinition['A']).to.deep.equal({
11+
parents: undefined,
12+
aliases: undefined,
13+
});
14+
expect(lineageDefinition['A.1']).to.deep.equal({
15+
parents: ['A'],
16+
aliases: undefined,
17+
});
18+
expect(lineageDefinition['AT.1']).to.deep.equal({
19+
parents: ['B.1.1.370'],
20+
aliases: ['B.1.1.370.1'],
21+
});
22+
});
23+
24+
it('should return the file as YAML', async () => {
25+
const result = await fetch(basePath + '/sample/lineageDefinition/pango_lineage', {
26+
headers: { Accept: 'application/yaml' },
27+
});
28+
const lineageDefinitionYaml = await result.text();
29+
30+
const expectedFileStart = `---
31+
A: {}
32+
A.1:
33+
parents:
34+
- "A"
35+
A.11:
36+
parents:
37+
- "A"`;
38+
39+
expect(lineageDefinitionYaml).to.match(new RegExp(`^${expectedFileStart}`));
40+
});
41+
});

lapis/src/main/kotlin/org/genspectrum/lapis/controller/ControllerDescriptions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const val AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION =
2121
considered."""
2222
const val INFO_ENDPOINT_DESCRIPTION = "Returns information about LAPIS."
2323
const val DATABASE_CONFIG_ENDPOINT_DESCRIPTION = "Returns the database configuration."
24+
const val LINEAGE_DEFINITION_ENDPOINT_DESCRIPTION = """Download the lineage definition file used for a certain column.
25+
This can be used to reconstruct the lineage tree.
26+
"""
2427
const val REFERENCE_GENOME_ENDPOINT_DESCRIPTION = "Returns the reference genome."
2528
const val ALIGNED_AMINO_ACID_SEQUENCE_ENDPOINT_DESCRIPTION =
2629
"""Returns a string of aligned amino acid sequences. Only sequences matching the specified

lapis/src/main/kotlin/org/genspectrum/lapis/controller/InfoController.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import org.genspectrum.lapis.logging.RequestIdContext
99
import org.genspectrum.lapis.model.SiloQueryModel
1010
import org.genspectrum.lapis.response.LapisInfo
1111
import org.genspectrum.lapis.response.LapisInfoFactory
12+
import org.genspectrum.lapis.silo.LineageDefinition
1213
import org.springframework.http.MediaType
1314
import org.springframework.web.bind.annotation.GetMapping
15+
import org.springframework.web.bind.annotation.PathVariable
1416
import org.springframework.web.bind.annotation.RequestMapping
1517
import org.springframework.web.bind.annotation.RestController
1618

1719
const val INFO_ROUTE = "/info"
1820
const val DATABASE_CONFIG_ROUTE = "/databaseConfig"
21+
const val LINEAGE_DEFINITION_ROUTE = "/lineageDefinition"
1922
const val REFERENCE_GENOME_ROUTE = "/referenceGenome"
2023

2124
@RestController
@@ -45,6 +48,15 @@ class InfoController(
4548
@Operation(description = DATABASE_CONFIG_ENDPOINT_DESCRIPTION)
4649
fun getDatabaseConfigAsJson(): DatabaseConfig = databaseConfig
4750

51+
@GetMapping(
52+
"$LINEAGE_DEFINITION_ROUTE/{column}",
53+
produces = [MediaType.APPLICATION_JSON_VALUE, APPLICATION_YAML_VALUE],
54+
)
55+
@Operation(description = LINEAGE_DEFINITION_ENDPOINT_DESCRIPTION)
56+
fun getLineageDefinition(
57+
@PathVariable("column") column: String,
58+
): LineageDefinition = siloQueryModel.getLineageDefinition(column)
59+
4860
@GetMapping(REFERENCE_GENOME_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
4961
@Operation(description = REFERENCE_GENOME_ENDPOINT_DESCRIPTION)
5062
fun getReferenceGenome(): ReferenceGenome = referenceGenome

lapis/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,6 @@ class SiloQueryModel(
178178
)
179179

180180
fun getInfo(): InfoData = siloClient.callInfo()
181+
182+
fun getLineageDefinition(column: String) = siloClient.getLineageDefinition(column)
181183
}

lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.genspectrum.lapis.silo
22

3+
import com.fasterxml.jackson.annotation.JsonInclude
34
import com.fasterxml.jackson.databind.ObjectMapper
45
import com.fasterxml.jackson.module.kotlin.readValue
56
import mu.KotlinLogging
67
import org.genspectrum.lapis.controller.LapisHeaders.REQUEST_ID
78
import org.genspectrum.lapis.logging.RequestContext
89
import org.genspectrum.lapis.logging.RequestIdContext
910
import org.genspectrum.lapis.response.InfoData
11+
import org.genspectrum.lapis.util.YamlObjectMapper
1012
import org.springframework.cache.annotation.Cacheable
1113
import org.springframework.http.HttpHeaders
1214
import org.springframework.http.HttpStatus
@@ -52,6 +54,12 @@ class SiloClient(
5254
dataVersion.dataVersion = info.dataVersion
5355
return info
5456
}
57+
58+
fun getLineageDefinition(column: String): LineageDefinition {
59+
log.info { "Calling SILO lineageDefinition for column '$column'" }
60+
61+
return cachedSiloClient.getLineageDefinition(column)
62+
}
5563
}
5664

5765
const val SILO_QUERY_CACHE_NAME = "siloQueryCache"
@@ -60,6 +68,7 @@ const val SILO_QUERY_CACHE_NAME = "siloQueryCache"
6068
open class CachedSiloClient(
6169
private val siloUris: SiloUris,
6270
private val objectMapper: ObjectMapper,
71+
private val yamlObjectMapper: YamlObjectMapper,
6372
private val requestIdContext: RequestIdContext,
6473
private val requestContext: RequestContext,
6574
) {
@@ -114,6 +123,16 @@ open class CachedSiloClient(
114123
)
115124
}
116125

126+
fun getLineageDefinition(column: String): LineageDefinition {
127+
val response = send(
128+
uri = siloUris.lineageDefinition(column),
129+
bodyHandler = BodyHandlers.ofString(),
130+
tryToReadSiloErrorFromBody = ::tryToReadSiloErrorFromString,
131+
) { it.GET() }
132+
133+
return yamlObjectMapper.objectMapper.readValue(response.body())
134+
}
135+
117136
private fun <ResponseBodyType> send(
118137
uri: URI,
119138
bodyHandler: HttpResponse.BodyHandler<ResponseBodyType>,
@@ -193,3 +212,11 @@ data class SiloErrorResponse(val error: String, val message: String)
193212
data class SiloInfo(
194213
val version: String,
195214
)
215+
216+
typealias LineageDefinition = Map<String, LineageNode>
217+
218+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
219+
data class LineageNode(
220+
val parents: List<String>?,
221+
val aliases: List<String>?,
222+
)

lapis/src/main/kotlin/org/genspectrum/lapis/silo/SiloUris.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import java.net.URI
66

77
@Component
88
class SiloUris(
9-
@Value("\${silo.url}") siloUrl: String,
9+
@Value("\${silo.url}") private val siloUrl: String,
1010
) {
1111
val query = URI("$siloUrl/query")
1212
val info = URI("$siloUrl/info")
13+
14+
fun lineageDefinition(column: String): URI = URI("$siloUrl/lineageDefinition/").resolve(column)
1315
}

lapis/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,48 @@ class SiloClientTest(
435435
assertThat(exception.message, containsString(errorMessage))
436436
}
437437

438+
@Test
439+
fun `get lineage definition`() {
440+
val columnName = "test_column"
441+
MockServerClient("localhost", MOCK_SERVER_PORT)
442+
.`when`(
443+
request()
444+
.withMethod("GET")
445+
.withPath("/lineageDefinition/$columnName")
446+
.withHeader("X-Request-Id", REQUEST_ID_VALUE),
447+
)
448+
.respond(
449+
response()
450+
.withStatusCode(200)
451+
.withBody(
452+
"""
453+
A: {}
454+
A.1:
455+
parents:
456+
- A
457+
B:
458+
aliases:
459+
- A.1.1
460+
parents:
461+
- A.1
462+
""".trimIndent(),
463+
),
464+
)
465+
466+
val actual = underTest.getLineageDefinition(columnName)
467+
468+
assertThat(
469+
actual,
470+
equalTo(
471+
mapOf(
472+
"A" to LineageNode(parents = null, aliases = null),
473+
"A.1" to LineageNode(parents = listOf("A"), aliases = null),
474+
"B" to LineageNode(parents = listOf("A.1"), aliases = listOf("A.1.1")),
475+
),
476+
),
477+
)
478+
}
479+
438480
companion object {
439481
@JvmStatic
440482
val mutationActions = listOf(

0 commit comments

Comments
 (0)