Repositories and resources
A Genomic Resource Repository (GRR) is a tree of directories, each of which
may be a resource: a genomic_resource.yaml config plus the data files
it names. In Python that tree is a
GenomicResourceRepo, and each
directory it serves is a
GenomicResource.
A GenomicResource is deliberately generic: it can tell you the
resource’s declared type through
get_type(), but it
has no behaviour specific to any of them — it reads config and files and
stops there. Turning one into a reference genome or a score is the job of the
typed builders described in the following pages, which is why every one of
them takes a repository and an id rather than a bare path.
Opening a repository
build_genomic_resource_repository()
called with no arguments builds the repository named by the user’s
environment — in a standard installation, the public IossifovLab GRR. Passing
a definition dictionary instead builds exactly the repository it describes:
from gain.genomic_resources.repository_factory import build_genomic_resource_repository
# the environment's default repository
grr = build_genomic_resource_repository()
# one specific directory, as a repository of its own
local = build_genomic_resource_repository({
"id": "local_grr",
"type": "directory",
"directory": "/data/my-grr",
})
The definition dictionary is the same structure the .grr_definition.yaml
file holds. Genomic resources and repositories documents its keys, the repository types available,
and the cache_dir key — caching is a key any type can carry, not a type
of its own.
Finding a resource
Two lookups differ only in what they do when nothing matches:
get_resource()
raises, and
find_resource()
returns None. Use get_resource when the id is a constant in your
program and its absence is a bug; use find_resource when the id came from
outside and absence is an ordinary outcome.
Both accept a version_constraint in the same spelling the annotation
configuration uses, so a script can pin the resource it was written against:
res = grr.get_resource("hg38/scores/phastCons100way", version_constraint=">=0")
print(res.get_full_id(), res.get_type())
A constraint nothing satisfies is a lookup failure, not a silently older
resource — get_resource raises and find_resource returns None,
the same way they treat an unknown id. Check the resource’s actual version
before pinning: many published resources are still at 0, so a plausible
looking >=1.0 will simply fail to resolve.
To go the other way — from a property to the resources that have it — use
search_resources(),
which takes a full-text term, a resource type, or a wildcard query over
resource ids and labels, and yields matches lazily:
for res in grr.search_resources(resource_type="position_score"):
print(res.get_id())
get_all_resources()
is the unfiltered form. Both are generators over a potentially large
repository, so prefer to consume them lazily rather than materialising a
list.
Reading a resource
A resource exposes its metadata through small accessors —
get_config(),
get_type(),
get_labels(),
get_description() —
and its files through a family of open_* methods that hide where the
resource actually lives. The same call works whether the repository is a
local directory, an HTTP mirror or an S3 bucket:
with res.open_raw_file("statistics/histogram_phastCons100way.json") as infile:
print(infile.read())
open_raw_file() is
the general one; the typed openers
(open_tabix_file(),
open_vcf_file(),
open_fasta_file(),
open_bigwig_file())
return the corresponding pysam or pyBigWig object, fetching the index
alongside the data file where the format needs one.
The manifest
Every resource carries a Manifest
— the list of its files with their sizes and checksums. It is what lets a
repository detect that a file has changed without reading it, and what a
cached or mirrored repository compares against when it decides whether its
copy is stale.
get_manifest()
returns it, building one if the resource has none. The build reads every
file, so where it is possible at all it is expensive — and it needs a
read-write protocol, so on a read-only mount a resource with no stored
manifest fails here rather than building one.
get_loaded_manifest()
is the cheap counterpart — it returns the stored manifest or None, and
never builds one.
API
- gain.genomic_resources.repository_factory.build_genomic_resource_repository(definition: dict | None = None, file_name: str | None = None) GenomicResourceRepo[source]
Build a GRR using a definition dict or yaml file.
- class gain.genomic_resources.repository.GenomicResourceRepo(repo_id: str)[source]
Abstract base class for genomic resource repositories.
A repository manages a collection of genomic resources, providing methods to discover, retrieve, and (for writable repos) create resources.
- Repositories can be:
Protocol-based: Direct access to a single storage backend
Group: Aggregates multiple child repositories
Cached: Wraps another repository with local caching
- All repositories support resource lookup with optional version constraints:
repo.get_resource(“hg19/genome”) # Latest version repo.get_resource(“hg19/genome”, “>=2.0”) # Version 2.0 or higher repo.get_resource(“hg19/genome”, “=2.1”) # Exact version 2.1
- Variables:
repo_id – Unique identifier for this repository
definition – Configuration dict used to create this repository
- property definition: dict[str, Any] | None
Get a copy of the repository configuration definition.
- Returns:
Deep copy of definition dict, or None if not set
- abstractmethod find_resource(resource_id: str, version_constraint: str | None = None, repository_id: str | None = None) GenomicResource | None[source]
Return one resource with id qual to resource_id.
If resource is not found, None is returned.
repository_idselects a repository by id under the same rule asget_resource()– a repository answers to its own id, and a falsy id is no filter.
- abstractmethod get_all_resources() Generator[GenomicResource, None, None][source]
Return a generator over all resource in the repository.
- abstractmethod get_resource(resource_id: str, version_constraint: str | None = None, repository_id: str | None = None) GenomicResource[source]
Return one resource with id qual to resource_id.
If resource is not found, exception is raised.
repository_idrestricts the lookup to the repository carrying that id, anywhere in this repository’s tree – including this repository itself: every repository answers to its own id, so passingrepo.repo_idis equivalent to passing nothing (#447). A falsyrepository_idis no filter at all.
- abstractmethod invalidate() None[source]
Clear cached state and force reload on next access.
Implementations should clear any cached resource lists, metadata, or file contents to ensure fresh data is loaded.
- property repo_id: str
Get the repository identifier.
- Returns:
Repository ID string
- abstractmethod search_resources(search_term: str | None = None, resource_type: str | None = None, resource_query: str | None = None) Generator[GenomicResource, None, list[tuple[str, str]] | None][source]
Search resources by FTS term, type and/or wildcard query.
All supplied filters conjoin.
The generator’s return value carries the
(repository id, reason)pairs of the children a group skipped while still answering (ADR 0012, gain#686);Noneand[]both mean nothing was skipped. Aforloop discards it, which is exactly right for a caller that does not present totals.
- search_resources_by_child(search_term: str | None = None, resource_type: str | None = None, resource_query: str | None = None) Generator[tuple[GenomicResourceRepo, GenomicResource], None, list[tuple[str, str]] | None][source]
Search, pairing each hit with the repository that serves it.
For a repository that serves resources itself the answer is always this one, which is what this implementation says. A group overrides it to name the child the resource actually came from, so a caller that has to label a hit –
grr_manage listprints the id beside every row – does not have to take a group apart to find out.The filters mean exactly what they mean for
search_resources(), which is the projection of this – and the return value carries the same skips.
- class gain.genomic_resources.repository.GenomicResource(resource_id: str, version: tuple[int, ...], protocol: ReadOnlyRepositoryProtocol | ReadWriteRepositoryProtocol, config: dict[str, Any] | None = None, manifest: Manifest | None = None)[source]
Represents a single genomic resource with metadata and file access.
A genomic resource is a versioned collection of data files with a configuration file (genomic_resource.yaml) that defines its type, description, and resource-specific settings.
- Common resource types include:
genome: Reference genome sequences
gene_models: Gene annotations and transcript models
position_score: Position-based genomic scores
allele_score: Variant effect scores
gene_score: Gene-level scores
- Variables:
resource_id – Unique identifier like “hg19/gene_models/refseq”
version (tuple[int, ...]) – Version tuple like (1, 2, 3)
config – Configuration dictionary from genomic_resource.yaml
proto – Repository protocol for accessing resource files
- get_config() dict[str, Any][source]
Return the resource configuration.
Raises
ValueErrorif the resource carries no config. The return type is not optional and this never returnsNone, so a caller has nothing to re-check (gain#1010).
- get_file_content(filename: str, *, uncompress: bool = True, mode: str = 't') Any[source]
Return the content of file in a resource.
- get_file_url(filename: str) str[source]
The URL of
filenamein this resource, per its protocol.A filesystem path for a directory repository, an
http(s)://ors3://URL otherwise. The name is validated; the file need not exist.
- get_full_id() str[source]
Return a string combining resource ID and version.
Returns a string of the form aa/bb/cc(3.2) for a genomic resource with id aa/bb/cc and version 3.2. If the version is 0 the string will be aa/bb/cc.
This is also the resource’s path component under a repository’s url: a protocol addresses the resource’s directory by joining this string onto the repository root, so the suffix is part of where a versioned resource is stored, not merely of how it is displayed.
- get_labels() dict[str, Any][source]
Return resource labels.
metaandmeta.labelsare both free-form YAML, so what is in either is whatever the curator wrote – a scalar, a list and an int are all things a resource can declare, and only the resource types that run the base schema are refused for it. Both levels are narrowed rather than trusted: a non-mapping reads as no labels and is reported, so that every caller sees a mapping whatever the resource says (gain#654). The outer level is narrowed byget_meta(), which everymetareader shares (gain#1004); this is the inner half of the two-tier narrowing described there, and it holds to the same never-validates, never-raises contract.The values are returned as written. How a value is read – a list as a set of alternatives, everything else as its
str()– islabel_alternatives().
- get_loaded_manifest() Manifest | None[source]
Return the resource manifest without ever building one.
get_manifest()falls back to building the manifest on a read-write protocol – an md5 scan of every byte of the resource that also writes.grr/*.statefiles, and that fails outright on a read-only GRR mount. A pure read path that merely wants to consult the manifest uses this instead and copes withNone.
- get_memo_key() tuple[str, str, str][source]
Return a key identifying everything this resource denotes.
For memoising an object built from a resource – gene models, a gene score, a gene set collection, a liftover chain. Two resources that would build different objects have different keys.
The config is part of the key because a resource at the repository root, spelled
".", takes its whole meaning from it: its id and repository url alone do not separate it from another root resource over the same directory. The repository url is part of the key too, so the same resource reached through two repositories is memoised once per repository.The key is finer than value identity, never coarser, so a miss costs a rebuild rather than a wrong answer. Raises
ValueErrorif the resource carries no config.
- get_meta() dict[str, Any][source]
Return the resource’s
metablock, narrowed to a mapping.metais free-form YAML, so what is there is whatever the curator wrote –meta: |followed by prose parses to a string, and only the resource types that run the base schema are refused for it. It is narrowed rather than trusted, so that every reader of the block sees a mapping whatever the resource says: a non-mapping reads as absent metadata and is reported.The narrowing is SHALLOW – it promises a mapping at the top level and says nothing about what any field inside holds, which is just as free-form. A reader of a field narrows that field itself, the way
get_labels()narrowsmeta.labelson top of this andget_descriptionsettles forstr()on whatever it finds.This is the single seam through which the
metablock is read – byget_description,get_summary,get_labelsand the FTS index-row collector alike – because narrowing it in one reader and not the others is what gain#1004 was:get_labelscoped while the two beside it raised a bareAttributeError, which aborted the repository-wide index build outright.Narrowing the block is not on its own enough to make every derived value agree. The index row used to collect
descriptionandsummaryout of this mapping with its own.getcalls, so it missed the description fall-backget_summary()applies and a resource carrying a description and no summary indexed an emptysummarycolumn (gain#1008). Both now derive through_description_in()and_summary_in(), which are the single spelling of what each field is: a reader holding this block derives from those rather than reaching into it, so a second spelling cannot drift from the accessors again.Reading never validates (ADR 0008) and never raises: this is on the path of every repository-wide walk – a label search, the index build,
grr_manage list– and one malformed resource must cost that walk only itself (gain#464, gain#503).
- open_fasta_file(filename: str, index_filename: str | None = None, compressed_index_filename: str | None = None) FastaFile[source]
Open a bgzipped fasta file and return a pysam.FastaFile.
- open_raw_file(filename: str, mode: str = 'rt', **kwargs: str | bool | None) IO[source]
Open a file in the resource and returns a File-like object.
- class gain.genomic_resources.repository.Manifest[source]
Manages file listings and checksums for a genomic resource.
A manifest maintains a catalog of all files in a resource with their sizes and MD5 checksums. This enables data integrity verification, efficient caching, and incremental updates.
The manifest is typically stored in a .MANIFEST file within the resource directory and is automatically loaded when accessing the resource.
- add(entry: ManifestEntry) None[source]
Add or update a manifest entry.
- Parameters:
entry – ManifestEntry to add to the manifest
- static from_file_content(file_content: str) Manifest[source]
Create a manifest from raw YAML file content.
- Parameters:
file_content – YAML-formatted string containing manifest entries
- Returns:
Manifest object with entries parsed from the content
- static from_manifest_entries(manifest_entries: list[dict[str, Any]]) Manifest[source]
Create a manifest from parsed manifest entry dictionaries.
- Parameters:
manifest_entries – List of dicts with ‘name’, ‘size’, ‘md5’ keys
- Returns:
Manifest object populated with the provided entries
- get_files() list[tuple[str, int]][source]
Get list of all files with their sizes.
- Returns:
List of (filename, size) tuples for all files in manifest
- names() set[str][source]
Get set of all filenames in the manifest.
- Returns:
Set of filenames tracked by this manifest