Skip to content

Commit 28ea0ff

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

File tree

9 files changed

+122
-4
lines changed

9 files changed

+122
-4
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ 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 |
55+
| 0.3.13 | 0.5.2 |
5456
| 0.3.13 | 0.5.2 |
5557
| 0.3.7 | 0.3.0 |
5658
| 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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
expect(lineageDefinition['A.1']).to.deep.equal({ parents: ['A'] });
12+
expect(lineageDefinition['AT.1']).to.deep.equal({
13+
aliases: ['B.1.1.370.1'],
14+
parents: ['B.1.1.370'],
15+
});
16+
});
17+
18+
it('should return the file as YAML', async () => {
19+
const result = await fetch(basePath + '/sample/lineageDefinition/pango_lineage');
20+
const lineageDefinitionYaml = await result.text();
21+
22+
const expectedFileStart = `A: {}
23+
A.1:
24+
parents:
25+
- A
26+
A.11:
27+
parents:
28+
- A`;
29+
30+
expect(lineageDefinitionYaml).to.match(new RegExp(`^${expectedFileStart}`));
31+
});
32+
});

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.genspectrum.lapis.controller.LapisHeaders.REQUEST_ID
77
import org.genspectrum.lapis.logging.RequestContext
88
import org.genspectrum.lapis.logging.RequestIdContext
99
import org.genspectrum.lapis.response.InfoData
10+
import org.genspectrum.lapis.util.YamlObjectMapper
1011
import org.springframework.cache.annotation.Cacheable
1112
import org.springframework.http.HttpHeaders
1213
import org.springframework.http.HttpStatus
@@ -52,6 +53,12 @@ class SiloClient(
5253
dataVersion.dataVersion = info.dataVersion
5354
return info
5455
}
56+
57+
fun getLineageDefinition(column: String): LineageDefinition {
58+
log.info { "Calling SILO lineageDefinition for column '$column'" }
59+
60+
return cachedSiloClient.getLineageDefinition(column)
61+
}
5562
}
5663

5764
const val SILO_QUERY_CACHE_NAME = "siloQueryCache"
@@ -60,6 +67,7 @@ const val SILO_QUERY_CACHE_NAME = "siloQueryCache"
6067
open class CachedSiloClient(
6168
private val siloUris: SiloUris,
6269
private val objectMapper: ObjectMapper,
70+
private val yamlObjectMapper: YamlObjectMapper,
6371
private val requestIdContext: RequestIdContext,
6472
private val requestContext: RequestContext,
6573
) {
@@ -114,6 +122,16 @@ open class CachedSiloClient(
114122
)
115123
}
116124

125+
fun getLineageDefinition(column: String): LineageDefinition {
126+
val response = send(
127+
uri = siloUris.lineageDefinition(column),
128+
bodyHandler = BodyHandlers.ofString(),
129+
tryToReadSiloErrorFromBody = ::tryToReadSiloErrorFromString,
130+
) { it.GET() }
131+
132+
return yamlObjectMapper.objectMapper.readValue(response.body())
133+
}
134+
117135
private fun <ResponseBodyType> send(
118136
uri: URI,
119137
bodyHandler: HttpResponse.BodyHandler<ResponseBodyType>,
@@ -193,3 +211,10 @@ data class SiloErrorResponse(val error: String, val message: String)
193211
data class SiloInfo(
194212
val version: String,
195213
)
214+
215+
typealias LineageDefinition = Map<String, LineageNode>
216+
217+
data class LineageNode(
218+
val parents: List<String>?,
219+
val aliases: List<String>?,
220+
)

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)