diff --git a/lapis-docs/astro.config.mjs b/lapis-docs/astro.config.mjs index 40d962809..1c4a78dad 100644 --- a/lapis-docs/astro.config.mjs +++ b/lapis-docs/astro.config.mjs @@ -90,6 +90,10 @@ export default defineConfig({ label: 'Response format', link: '/concepts/response-format', }, + { + label: 'Customizable FASTA headers', + link: '/concepts/customizable-fasta-headers', + }, { label: 'Data versions', link: '/concepts/data-versions', @@ -113,6 +117,10 @@ export default defineConfig({ label: 'Ambiguous symbols', link: '/concepts/ambiguous-symbols', }, + { + label: 'Lineage queries', + link: '/concepts/lineage-queries', + }, { label: 'Pango lineage query', link: '/concepts/pango-lineage-query', diff --git a/lapis-docs/src/config.ts b/lapis-docs/src/config.ts index b6cf3c0da..e6b37284a 100644 --- a/lapis-docs/src/config.ts +++ b/lapis-docs/src/config.ts @@ -14,7 +14,7 @@ export type MetadataType = export type Metadata = { name: string; type: MetadataType; - generateLineageIndex?: boolean; + generateLineageIndex?: string | null; }; export type Feature = { @@ -55,7 +55,12 @@ export function hasFeature(feature: string): boolean { export function hasPangoLineage(config: Config): boolean { return config.schema.metadata.some( (m) => - m.generateLineageIndex === true && + m.generateLineageIndex !== undefined && + m.generateLineageIndex !== null && (m.name.toLowerCase().includes('pangolineage') || m.name.toLowerCase().includes('pango_lineage')), ); } + +export function hasLineageFields(config: Config): boolean { + return config.schema.metadata.some((m) => m.generateLineageIndex !== undefined && m.generateLineageIndex !== null); +} diff --git a/lapis-docs/src/content/docs/concepts/advanced-query.mdx b/lapis-docs/src/content/docs/concepts/advanced-query.mdx index 531a93909..e165cf3ba 100644 --- a/lapis-docs/src/content/docs/concepts/advanced-query.mdx +++ b/lapis-docs/src/content/docs/concepts/advanced-query.mdx @@ -129,7 +129,7 @@ The syntax is as follows, where `expr1`, ..., `expr5` are any valid expressions: ## Examples -Variant queries can be sent in GET and POST requests just like standard filters. +Advanced queries can be sent in GET and POST requests just like standard filters. - GET: https://lapis.cov-spectrum.org/open/v2/sample/aggregated?advancedQuery=501T%20and%20country=Switzerland diff --git a/lapis-docs/src/content/docs/concepts/customizable-fasta-headers.mdx b/lapis-docs/src/content/docs/concepts/customizable-fasta-headers.mdx new file mode 100644 index 000000000..a532972f4 --- /dev/null +++ b/lapis-docs/src/content/docs/concepts/customizable-fasta-headers.mdx @@ -0,0 +1,55 @@ +--- +title: Customizable FASTA headers +description: Customize the header line of FASTA records returned by sequence endpoints +--- + +When requesting sequences as FASTA, you can customize the header line of each record by setting the +`fastaHeaderTemplate` request property. +The parameter is ignored if the data format is not FASTA. + +The template allows placeholders in curly braces: + +- `{}` — replaced with the value of the metadata field for that sequence (e.g. `{country}`). + The instance's primary key field (configured by the maintainer) can be used as a placeholder in the same way. +- `{.segment}` — replaced with the segment name. Only valid on nucleotide sequence endpoints. +- `{.gene}` — replaced with the gene name. Only valid on amino acid sequence endpoints. + +If a metadata value is `null`, the placeholder is replaced with an empty string. + +If `fastaHeaderTemplate` is not set, LAPIS uses a default that includes the primary key (and the gene name +for amino acid sequences, and the segment name for multi-segmented nucleotide sequences). + +The template contains characters (`{`, `}`, `|`) that are not URL-safe. +When sending the request as a GET, the value of `fastaHeaderTemplate` must be URL-encoded. +In JavaScript, this can be done with the +[encodeURIComponent()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) +function. POST requests can carry the template unencoded in the JSON body. + +## Examples + +Assuming the instance's primary key field is named `accession`, include it together with a separator and two +metadata fields. As a POST request, the template can be sent unencoded: + +```http +POST /sample/alignedNucleotideSequences + +{ + "fastaHeaderTemplate": "{accession}|{country}|{date}" +} +``` + +The same query as a GET request, with the template URL-encoded: + +``` +GET /sample/alignedNucleotideSequences?fastaHeaderTemplate=%7Baccession%7D%7C%7Bcountry%7D%7C%7Bdate%7D +``` + +For amino acid sequences, include the gene name (useful when requesting multiple genes in one call): + +```http +POST /sample/alignedAminoAcidSequences + +{ + "fastaHeaderTemplate": "{accession}|{.gene}" +} +``` diff --git a/lapis-docs/src/content/docs/concepts/lineage-queries.mdx b/lapis-docs/src/content/docs/concepts/lineage-queries.mdx new file mode 100644 index 000000000..1fcd01146 --- /dev/null +++ b/lapis-docs/src/content/docs/concepts/lineage-queries.mdx @@ -0,0 +1,48 @@ +--- +title: Lineage queries +description: Filtering lineage fields and their descendants +--- + +import { OnlyIf } from '../../../components/OnlyIf.tsx'; +import { getConfig, hasLineageFields } from '../../../config.ts'; +import { Code } from '@astrojs/starlight/components'; + +export const lineageField = 'lineageField'; + +{/* prettier-ignore */} + +:::note +This LAPIS instance does not provide lineage queries. +::: + + +A lineage field stores values that form a hierarchy. +For example, if `B.1.1` is a sub-lineage of `B.1`, then a query for `B.1*` can include records assigned to `B.1`, +`B.1.1`, `B.1.1.7`, and other descendants. + +## Exact Matches + +Without a wildcard, lineage filters match only the exact lineage value: + + + +This returns records whose lineage field value is exactly `B.1.1`. + +## Including Descendants + +To include sub-lineages, add `*` at the end of the lineage: + + + +`B.1.1*` and `B.1.1.*` are equivalent. +Both match `B.1.1` itself and descendants such as `B.1.1.7`. + +## Advanced Queries + +Lineage-indexed fields can also be used in [advanced queries](../concepts/advanced-query): + + + +In POST requests, use the same expression as the `advancedQuery` value: + + diff --git a/lapis-docs/src/content/docs/concepts/pango-lineage-query.mdx b/lapis-docs/src/content/docs/concepts/pango-lineage-query.mdx index ebab62c97..44a407bb4 100644 --- a/lapis-docs/src/content/docs/concepts/pango-lineage-query.mdx +++ b/lapis-docs/src/content/docs/concepts/pango-lineage-query.mdx @@ -9,7 +9,7 @@ import { getConfig, hasPangoLineage, hasFeature } from '../../../config.ts'; {/* prettier-ignore */} :::note -This feature is not available in this LAPIS instance, because none of the fields seems to be a Pango lineage field. +This feature is not available in this LAPIS instance. ::: @@ -17,6 +17,9 @@ Pango lineage names inherit the hierarchical nature of genetic lineages. For exa More information about the pango nomenclature can be found on the website of the [Pango network](https://www.pango.network/). +LAPIS supports Pango lineage queries for Pango lineage fields that have a lineage index. +For general lineage-indexed fields, see [lineage queries](../concepts/lineage-queries). + With the pangoLineage filter and in [variant queries](../concepts/variant-query), it is possible to not only filter for a very specific lineage but also to include its sub-lineages. To include sub-lineages, add a `*` at the end. diff --git a/lapis-docs/src/content/docs/concepts/response-format.mdx b/lapis-docs/src/content/docs/concepts/response-format.mdx index 10dd76196..bd63e5086 100644 --- a/lapis-docs/src/content/docs/concepts/response-format.mdx +++ b/lapis-docs/src/content/docs/concepts/response-format.mdx @@ -27,6 +27,9 @@ You can read line by line without loading the entire file into memory. Since every line is a valid JSON object, it is usually easier to handle programmatically than FASTA. ::: +The header line of FASTA records can be customized via the `fastaHeaderTemplate` request property. +See [Customizable FASTA headers](../concepts/customizable-fasta-headers) for details. + ## Example To understand the response of the `nucleotideMutation` endpoint, refer to the relevant section in the Swagger UI: diff --git a/lapis-docs/src/content/docs/index.mdx b/lapis-docs/src/content/docs/index.mdx index 4d1f234c6..470335874 100644 --- a/lapis-docs/src/content/docs/index.mdx +++ b/lapis-docs/src/content/docs/index.mdx @@ -1,15 +1,50 @@ --- title: Welcome to LAPIS -description: Get started with LAPIS +description: Lightweight API for Sequences — query genomic data over HTTP template: splash hero: - tagline: Instance for SARS-CoV-2 + tagline: A fast, lightweight API for querying genomic sequence data actions: - - text: Introduction + - text: Get started link: getting-started/introduction icon: right-arrow variant: primary - - text: LAPIS on GitHub + - text: Source code on GitHub link: https://github.com/GenSpectrum/LAPIS icon: external + variant: minimal --- + +## What is LAPIS? + +LAPIS (**Lightweight API for Sequences**) is an open-source HTTP API for querying large +collections of pathogen genome sequences. It is built for researchers, public health +analysts and developers who need to filter, aggregate or download genomic data +programmatically. + +Under the hood, LAPIS delegates storage and fast filter execution to +[**SILO**](https://github.com/GenSpectrum/LAPIS-SILO), a purpose-built sequence database. + +## Known instances + +LAPIS is deployed in a number of production systems: + +- **[CoV-Spectrum](https://cov-spectrum.org) and [GenSpectrum](https://genspectrum.org)** are interactive dashboards for + analyzing virus variants and mutations, backed by a LAPIS instance over INSDC and Pathoplexus data. +- **[Loculus](https://loculus.org)** is an open-source software for managing and sharing microbial genome data that uses + LAPIS and SILO as its query engine. Production Loculus deployments include + [Pathoplexus](https://pathoplexus.org), an initiative to facilitate sharing of + genetic sequencing data for human viruses of public health importance. +- **[W-ASAP](https://db.wasap.genspectrum.org/)** uses a LAPIS instance for wastewater sequencing data, supporting queries over mutation abundances + from Swiss wastewater samples. + +If you are operating a public LAPIS instance and want it to be listed here, please open an issue on +[GitHub](https://github.com/GenSpectrum/LAPIS). + +## Citation + +If you use LAPIS in your work, please cite: + +> Chen, C., Taepper, A., Engelniederhammer, F., Kellerer, J., Roemer, C. & Stadler, T. "LAPIS is a fast web API for +> massive open virus sequencing data" BMC Bioinformatics (2023); doi: +> [10.1186/s12859-023-05364-3](https://doi.org/10.1186/s12859-023-05364-3) diff --git a/lapis-docs/tests/configGenerator.page.ts b/lapis-docs/tests/configGenerator.page.ts index c2e0bdb56..93c32abc2 100644 --- a/lapis-docs/tests/configGenerator.page.ts +++ b/lapis-docs/tests/configGenerator.page.ts @@ -24,7 +24,7 @@ export class ConfigGeneratorPage { public async goto() { await this.page.goto(baseUrl); - await this.page.getByRole('link', { name: 'Introduction' }).click(); + await this.page.getByRole('link', { name: 'Get started' }).click(); await this.page.getByRole('link', { name: 'Generate your config', exact: true }).click(); } diff --git a/lapis-docs/tests/docs.spec.ts b/lapis-docs/tests/docs.spec.ts index 211cb5d3e..e17d8bfac 100644 --- a/lapis-docs/tests/docs.spec.ts +++ b/lapis-docs/tests/docs.spec.ts @@ -44,11 +44,13 @@ const conceptsPages = prependToRelativeUrl( { title: 'Authentication', relativeUrl: '/authentication' }, { title: 'Request methods: GET and POST', relativeUrl: '/request-methods' }, { title: 'Response format', relativeUrl: '/response-format' }, + { title: 'Customizable FASTA headers', relativeUrl: '/customizable-fasta-headers' }, { title: 'Data versions', relativeUrl: '/data-versions' }, { title: 'Request ID', relativeUrl: '/request-id' }, { title: 'Mutation filters', relativeUrl: '/mutation-filters' }, { title: 'Insertion filters', relativeUrl: '/insertion-filters' }, { title: 'Ambiguous symbols', relativeUrl: '/ambiguous-symbols' }, + { title: 'Lineage queries', relativeUrl: '/lineage-queries' }, { title: 'Pango lineage query', relativeUrl: '/pango-lineage-query' }, { title: 'Variant query', relativeUrl: '/variant-query' }, { title: 'Advanced query', relativeUrl: '/advanced-query' }, @@ -130,7 +132,7 @@ test.describe('The documentation', () => { test('should show all expected pages via link in navigation', async ({ page }) => { await page.goto(baseUrl); - await page.getByRole('link', { name: 'Introduction' }).click(); + await page.getByRole('link', { name: 'Get started' }).click(); await expect(page).toHaveTitle(/^Introduction/); await expect(page).toHaveURL(thatDoesNotEndWithSlash); diff --git a/lapis-docs/tests/queryGenerator.page.ts b/lapis-docs/tests/queryGenerator.page.ts index 26b4ce500..eca32b59e 100644 --- a/lapis-docs/tests/queryGenerator.page.ts +++ b/lapis-docs/tests/queryGenerator.page.ts @@ -23,7 +23,7 @@ export class QueryGeneratorPage { public async goto() { await this.page.goto(baseUrl); - await this.page.getByRole('link', { name: 'Introduction' }).click(); + await this.page.getByRole('link', { name: 'Get started' }).click(); await this.page.getByRole('link', { name: 'Generate your request', exact: true }).click(); }