Skip to Content
GuidesFetching your inputs

Fetching your inputs

What you will do: replace the download step at the top of a pipeline with a declaration, pin it, and watch the second run fetch nothing.

Before you start: a pipeline that runs. See building a pipeline.

Declare it instead of downloading it

A pipeline that starts with a path has already lost the argument: the path proves nothing about what is at the end of it.

hxb2 = lg.reference("data/refs/HXB2.fasta", organism="HIV-1", build="HXB2")

Say where it comes from instead:

import ligase as lg from ligase.types import Reference hxb2 = lg.remote( "ncbi:K03455.1", kind=Reference, organism="HIV-1", build="HXB2", )

ligase run fetches it, reads it as a Reference, and hands back the same object lg.reference would have — contigs, checksums, dimensions and all. The file lands in the content-addressed store under .ligase/, named for the hash of its bytes.

kind is required and is not inferred from the locator. It decides how the file is read, therefore what its digest is over, and what the result is typed as — so a step annotated Reference will not accept reads by mistake.

Pin it

Run it once with no digest, and read back what arrived:

print(hxb2.origin.digest)
1f460b25ab23b95345d579c234c43ca6d0abf7c1adf6aeffb43b974b2b1941ad

Paste that into the call and every later run is a checked one:

hxb2 = lg.remote( "ncbi:K03455.1", kind=Reference, digest="1f460b25ab23b95345d579c234c43ca6d0abf7c1adf6aeffb43b974b2b1941ad", organism="HIV-1", build="HXB2", )

A digest that does not match is an error before any step runs:

diagnosticligase check — no step ran
the remote `http://localhost:8731/HXB2.fasta` has moved: it was pinned to 0000000000000000000000000000000000000000000000000000000000000000 and it is now e20d21a716bd052c9be3db97dd7e77cfa35772fa31f46ab2b2945942e7a9f8c3. This is a change in what the data means, not a reformatting, so check what changed at the source before updating the pin.

The digest is over what the file means rather than over its bytes, so a source that reflows a FASTA or rewords a defline does not break the pin. That is reported as a note instead. See where the data came from for why.

The four schemes

lg.remote("sra:SRR8525904", kind=Reads, samples=["SRR8525904"]) lg.remote("ncbi:K03455.1", kind=Reference, organism="HIV-1") lg.remote("ensembl:release-110/fasta/homo_sapiens/README", kind=...) lg.remote("https://example.org/panel.tsv", kind=EntitySet)
SchemeWhat it namesHow many files
sra:an SRA run accession, served by ENAtwo — a mate pair
ncbi:an NCBI nucleotide accession, fetched as FASTAone
ensembl:a path under Ensembl’s public treeone
https:an exact URLone

Anything else is an error naming the four:

diagnosticligase check — no step ran
`ftp://ftp.example.org/HXB2.fasta` names the scheme `ftp`, which Ligase does not have. The schemes are sra:, ncbi:, ensembl: and https:

A loopback address may be written http://, which is the same scheme by another name: TLS protects a channel against something sitting in it, and between two ends of a loopback socket there is nothing to sit in. That is what lets you point a pipeline at a mock archive on your own machine.

Reads need their sample named

reads = lg.remote( "sra:SRR8525904", kind=Reads, digest="a7800f3d…", organism="HIV-1", samples=["SRR8525904"], # required )

A local FASTQ’s sample identifier comes from its filename. A fetched one is stored under the hash of its bytes, which is no sample identifier at all — so the engine asks rather than taking a hash for a sample. It will not default to the run accession either, because one sample can have several runs and quietly treating a run as a sample is the sort of wrong answer this engine exists to refuse.

Running offline

Once an input is in the store with a matching digest, nothing is fetched. You can make that a rule rather than a happy accident:

from ligase.remotes import configure configure(offline=True)

Now a remote the store holds resolves as usual and anything else is an error saying so. This is what a reviewer with no connection wants, and what makes “the second run needs no network” a thing you can check rather than hope for.

configure also takes cache=, for a store somewhere other than ./.ligase. Call it before the pipeline is built, because that is when remotes resolve — ligase run --cache DIR and ligase check --cache DIR do it for you.

What ends up in the write-up

Both the locator and the date are in the methods paragraph, and neither is something a reader could work out from anything else:

Input data was retrieved from `sra:SRR8525904` on 23 October 2026 (BLAKE3 a7800f3dace6464faa08c34ce765f0398ab883c76c5475102318dae6c3d21923) and `ncbi:K03455.1` on 23 October 2026 (BLAKE3 1f460b25ab23b95345d579c234c43ca6d0abf7c1adf6aeffb43b974b2b1941ad). Each is pinned by the digest of its canonical form, so a rerun anywhere verifies the same bytes before any step runs.

A reproducibility bundle names each one in remotes.tsv — the locator and the digest, not the bytes. That is what pinned means: anyone can obtain it from the source and check it against the digest recorded there, so copying gigabytes of reads into every bundle would buy nothing.

ligase audit reports each remote as pinned, beside the step verdicts rather than folded into them. A pipeline whose every step is deterministic can still rest on an input nobody else can obtain, and that is worth reading rather than averaging away.

Seeing it work

The reference demo is built this way. With a source checkout:

ligase run examples/hiv-resistance/pipeline.py

There is nothing to run first. The pipeline declares seventeen remotes — HXB2 and eight paired runs — and examples/hiv-resistance/pipeline.py is also the manifest of their pins.

Next

Last updated on