API reference¶
Generated from the source. Each entry carries the signature, the argument types and the attributes exactly as the code declares them.
The written pages come first. They cover the same calls in the order you would make them: Assembly, Sequences and regions, Annotations and Aligner indexes for working with a genome; Transcription factors, Motifs, Gene identifiers and Homology for the tables the package ships; and CLI overview for the shell. Come here once you know the name you want.
Each section below is one package's public API: what its __init__.py re-exports, and
nothing else. A name you can only reach through a submodule path is internal, and it can
move or disappear between releases.
| Package | What it holds |
|---|---|
genome |
Genome, Region, the sequence types, and the results the common calls return |
genome.aligner |
The STAR and chromap index builders |
genome.annotation |
Registering a GTF and asking an annotation for gene ids |
genome.assembly |
The assembly table, the files on disk, and chimera naming |
genome.homology |
Ensembl Compara ortholog sets |
genome.store |
The errors an assembly directory or a prepared set raises |
genome.tf |
The shipped table linking transcription factors to motifs |
genome.tf.motif |
JASPAR matrices, scanning, and reading hit files |
genome.xref |
Identifier conversion and symbol matching |
genome ¶
liulab-genome: handling genomic files (metadata, processing, feature extraction).
AnnotationMetadata
dataclass
¶
AnnotationMetadata(
assembly: str,
name: str,
provider: str,
version: str,
url: str,
sha256: str | None = None,
default: bool = False,
)
One annotation the lab supports for one assembly (one row of the annotation table).
Keyed by assembly plus name — the Registered name the annotation is
addressed by everywhere — and carrying enough to register it from that name alone:
who publishes it, which release, where to fetch it, and what the unpacked GTF
that source yields hashes to. A complete record is also what the
registration functions accept in place of the table's own row.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly this annotation belongs to — an annotation belongs to exactly one. |
name |
str
|
The Registered name, unique within the assembly, e.g. |
provider |
str
|
Who publishes it: |
version |
str
|
The provider's own release identifier, e.g. |
url |
str
|
Where the GTF is fetched from. |
sha256 |
str or None
|
Digest of the unpacked GTF, or |
default |
bool
|
Whether this is the assembly's Default annotation. |
Examples:
>>> record = AnnotationMetadata(
... assembly="sacCer3",
... name="ensgene_v101",
... provider="UCSC",
... version="ensGene.v101",
... url="https://hgdownload.soe.ucsc.edu/goldenPath/sacCer3/bigZips/genes/sacCer3.ensGene.gtf.gz",
... )
>>> record.provider
'UCSC'
>>> record.default # not the default unless the row says so
False
from_row
classmethod
¶
Build a record from one row of an annotation table.
:meth:AssemblyMetadata.from_row for the annotation table's own columns, and
the same rules — with one more: default is a flag column, where a blank cell
is the real answer no rather than an unknown.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Column name to cell, as :meth: |
required |
Returns:
| Type | Description |
|---|---|
AnnotationMetadata
|
The record the row spells. |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
As :meth: |
Examples:
AnnotationRegistry ¶
AnnotationRegistry(
assembly_dir: AssemblyDir,
*,
chrom_sizes: str | Path | None = None,
default: str | None = None,
)
One Assembly's annotations, the state each is in, and the acts that add one.
An annotation directory is registered, broken, offered but not begun, or nothing at all, and every useful question about one is a question about that four-way state: what may a caller name, what may it be handed the path of, which is the Default annotation, what does a surface print, what does a name nobody registered earn as an error. This settles all four once, at construction, and answers from that — so the state is assembled in one place rather than wherever it is needed.
Bound to one assembly and carried, never re-derived: the Assembly dir comes in as
an :class:~genome.assembly.registration.AssemblyDir, so a registry cannot file an
annotation somewhere other than where the caller that built it is looking, and the
chrom.sizes every GTF is checked against comes in beside it rather than being
guessed from the layout.
Reading is cheap and safe: nothing here is created, fetched or built by asking, an
assembly with no directory at all answers emptily, and one broken annotation is
reported rather than raised over. Only :meth:register and :meth:register_path
write, and both fold what they wrote back in, so the four states stay current without
reading the disk again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_dir
|
AssemblyDir
|
The assembly this registry is for, and where its |
required |
chrom_sizes
|
str or Path
|
The assembly's |
None
|
default
|
str
|
A Default annotation the caller chose, which wins over the table's flag and
need not be registered. See :func: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly every annotation here belongs to. |
Examples:
>>> registry = AnnotationRegistry.locate("sacCer3", "/tmp/definitely-not-an-assembly")
>>> registry.registered
[]
>>> registry.default # the table's flag, registered or not
'ensgene_v101'
registered
property
¶
The Registered names on this machine, in directory-name order.
What is here, as against :attr:offered, which is what the lab supports, and
:attr:broken, which is what is here and cannot be trusted.
broken
property
¶
The annotation directories here that cannot be trusted as finished.
What :attr:registered leaves out, and between the two every directory under
gtf/ is accounted for. Each entry says what is wrong and names the one command
that repairs it.
offered
property
¶
The annotation table's rows for this assembly, in table order.
What the lab supports, whether or not anyone has registered it. Empty for an assembly the table offers nothing for, which is legal: it is a cross-reference rather than an allow-list.
default
property
¶
Name of the Default annotation, or None when nothing decides one.
:func:default_annotation's answer for this assembly, settled when the registry
was built. It may name an annotation nobody has registered here — the normal state
of a fresh machine — so it is :meth:path that says whether one exists. A default
already decided is never displaced by a later registration.
locate
classmethod
¶
locate(
assembly: str,
cache_dir: str | Path | None = None,
*,
default: str | None = None,
) -> AnnotationRegistry
Return the registry for assembly, wherever the layout says its files live.
The assembly-addressed way in, and the only one the CLI has: a name and at most a
directory override. :meth:~genome.assembly.registration.AssemblyDir.locate is where
that override rule lives, and the chrom.sizes is the one that layout names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to open the registry of, e.g. |
required |
cache_dir
|
str or Path
|
An explicit Assembly dir, overriding the Data dir layout. |
None
|
default
|
str
|
A Default annotation the caller chose. |
None
|
Returns:
| Type | Description |
|---|---|
AnnotationRegistry
|
Its registry. Nothing is created and nothing is fetched. |
Examples:
path ¶
Return the GTF file path of the annotation registered as name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name to resolve. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
Path to the placed |
Raises:
| Type | Description |
|---|---|
AnnotationNotRegisteredError
|
If nothing of that name is registered here. The four-way state decides what
the message says next: the command that registers |
Examples:
register ¶
register(
name: str,
*,
force: bool = False,
progressbar: bool = True,
metadata: AnnotationMetadata | None = None,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> GtfAnnotation
Register the annotation the table lists for this assembly as name.
Naming an annotation is enough: where its GTF comes from and which digest it must match are the curated table's to know. The row's URL is fetched into the working area, the unpacked GTF is checked against the sha256 the row pins — so a GTF that is not the pinned one never reaches the annotation directory — the gffutils database is built, and the record is written last.
Its chromosome names are checked too, against this registry's chrom.sizes and
while the GTF is still in the working area: every name the GTF uses must be one the
assembly carries, so an Ensembl-spelled GTF registered against a UCSC-spelled
assembly fails in seconds rather than after the minutes the database build takes.
The reverse is not required — an assembly may carry scaffolds the annotation never
mentions. An assembly with no chrom.sizes yet has nothing to check against, and
the record says so in details["chromosomes_checked"] — with
details["chromosomes_unchecked_because"] saying whether that was for want of a
chrom.sizes or because the caller stood the check down.
An annotation that already has a valid record is returned silently: nothing is
fetched, nothing is rebuilt and nothing is warned about. A directory that cannot be
trusted — files with no record, or a record that disagrees with disk — raises,
naming genome annotation register <assembly> <name> --force. That is
what force=True is: it skips the question, keeps a GTF whose digest can be shown
to be the pinned one, and fetches the source again when it cannot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name the table lists, e.g. |
required |
force
|
bool
|
Register again from scratch, repairing a directory that raises. |
False
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
metadata
|
AnnotationMetadata
|
A complete annotation record to use instead of the curated table's row. Omit it and the row is looked up here. |
None
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
GtfAnnotation
|
The registered annotation's name and its two file paths. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the table lists no annotation |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry; the message lists them and names the usual cause. |
ChecksumMismatchError
|
If the row pins a sha256 and the unpacked GTF is not it; the message names both digests. |
UnfinishedRegistrationError
|
If the annotation's directory holds files but no record. |
RegistrationMismatchError
|
If its record disagrees with what is on disk. |
Examples:
register_path ¶
register_path(
gtf: str | Path,
name: str,
*,
force: bool = False,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> GtfAnnotation
Register the GTF at gtf under name and build its gffutils database.
The escape hatch for an annotation the curated table does not list —
:meth:register is the way in for one it does. A gzipped (.gz) source is
decompressed into the registered <name>.gtf; a plain GTF is copied as-is. The
digest recorded is of the placed GTF, since an unlisted annotation has no pinned
digest to compare against.
Its chromosome names are checked against this registry's chrom.sizes before
anything is created, so a GTF that does not line up leaves the annotation directory
exactly as it was found. Knowing the assembly is what buys that: the file is found
rather than passed, so an unlisted GTF is held to the same check a listed one gets.
Registering something already registered returns it silently, and a directory that
cannot be trusted raises naming genome annotation register-gtf <assembly> <gtf> <name>
--force, exactly as :meth:register does for a listed one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gtf
|
str or Path
|
Path to the source GTF, plain or |
required |
name
|
str
|
The Registered name to address it by, unique within the assembly. |
required |
force
|
bool
|
Register again from scratch — the repair for a directory that raises. |
False
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
GtfAnnotation
|
The registered annotation's name and its two file paths. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry. |
RegistrationError
|
If the annotation's directory cannot be trusted as finished. |
Examples:
status ¶
Report what this assembly's table offers against what is registered here.
Two questions with two answers, joined for one reader: the table's rows say what
the lab supports, the disk says what is on this machine, and every row carries
which of the two it is. The command behind it is genome annotation list.
A third answer rides along, because this is where a reader would look for it: a
directory that cannot be trusted is broken rather than registered. Nothing
raises — reporting a broken annotation is the point, and one of them must not cost
the rest.
Returns:
| Type | Description |
|---|---|
AnnotationStatus
|
The assembly, its directory, the Default annotation's name, and one
:class: |
Examples:
gene_list ¶
Return the genes one registered annotation puts in category.
The genes come from the Curated gene list shipped for that annotation and never from the GTF's own biotype attribute, which is spelled two ways across four publishers, carries three taxonomies that do not agree, and is absent altogether from some annotations. Nothing here knows a category vocabulary: which categories exist is what the curated list declares.
The annotation must be registered here — it is resolved through :meth:path,
so an unregistered name earns the error that names the command registering it.
The curated list is then held to the assembly it was curated against, since a name
is unique only within its assembly and a list found by name alone is not yet known
to be about this reference.
For a Merged annotation the record's merged_from says who contributed, and
each contributor's own curated list answers for its own Component: the result
carries one source per contributor that declares the category, so a caller counting
worm ribosomal RNA can drop the E. coli entry. A contributor that does not
declare it is simply absent — a bacterium has no mitochondria, and that is not a
failure.
There is no empty answer. An annotation nothing ships a list for, and one whose list does not declare this category, are different facts and each raises an error of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
The Gene category to ask for, as the curated list spells it — |
required |
name
|
str
|
The Registered name to ask about. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
GeneList
|
The category, its gene ids, and one
:class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneCategoriesError
|
If no curated list ships for that annotation — nothing can be asked of it, which is not the same answer as its having no genes in this category. |
GeneCategoryNotDeclaredError
|
If it declares categories and not this one; the message lists the ones it does. |
GeneListAssemblyMismatchError
|
If the curated list found under that name was curated against another assembly, in which case it must not answer here. |
Examples:
gene_lists ¶
Return every Gene category one registered annotation declares.
:meth:gene_list for all of them at once, in the order the curated lists spell
them — and for a Merged annotation, each contributor's own order, contributors
first-listed first. Everything :meth:gene_list says about resolution, the
assembly guard and attribution holds here.
Never an empty tuple. An annotation that declares nothing raises rather than
answering emptily, which is the whole distinction this surface exists to keep: a
caller that got () could not tell no categories are declared from every
category is empty, and no declared category is ever empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name to ask about. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of GeneList
|
One entry per declared category, in declaration order. Never empty. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneCategoriesError
|
If no curated list ships for that annotation. |
GeneListAssemblyMismatchError
|
If a curated list found by name was curated against another assembly. |
Examples:
resolve_gene_ids ¶
Return the gene ids one registered annotation carries for each Gene id stem.
A stem is a gene id with its version dropped — ENSG00000123456 for
ENSG00000123456.7 — which is how every published table keyed by gene arrives,
and never how a GENCODE Annotation spells the same gene. This is the crossing:
every gene id in the Annotation database is reduced to its own stem, and a stem
answers with every gene id that reduced to it. An id carrying no version is its own
stem, so an annotation whose ids were never versioned — WormBase's, SGD's — resolves
each of its genes to itself and is untouched by an Ensembl-shaped assumption.
Every id, and never a chosen one. One stem naming two gene ids is not a
malformed annotation: gencode_v50lift37 has nine such stems, eight of them
pseudoautosomal genes carrying a _PAR_Y copy, and a resolver taking the first
would hand back the X copy of a Y gene without saying it had chosen. So the answer
is a mapping to all of them, ascending.
Nothing is dropped. Stems this annotation carries no gene for come back in
:attr:~genome.annotation.stems.ResolvedGeneIds.unresolved, so a caller resolving
a few thousand at once
can see which of them this annotation does not have rather than counting the
answer and wondering.
The annotation must be registered here, and a Merged annotation is read
exactly as any other: it has one database of its own, holding both components' gene
features under the components' own gene ids — a merge rewrites seqnames and never a
gene_id — so a stem naming a gene in each component answers with both, which is
the same rule as the pseudoautosomal one and needs no attribution to apply it.
One indexed pass over the database's gene features answers the whole call, however many stems it was handed; nothing reads the GTF, and no annotation is held in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stems
|
iterable of str
|
The Gene id stems to resolve, in the order they should come back. Repeats are asked once. Pass them all at once: the cost is the pass, not the stem. |
required |
name
|
str
|
The Registered name to resolve against. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
ResolvedGeneIds
|
The stems that named gene ids, mapped to every id each names, and the stems that named none. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneFeaturesError
|
If its database holds no gene at all — every stem would otherwise resolve to nothing, which is a different fact and must not be reported as this one. |
Examples:
CuratedGeneListError ¶
Bases: ValueError
A shipped curated gene list cannot be read, so it is not allowed to answer.
A packaging defect and not a caller error: these files ship inside the package,
so bad JSON, a missing key, a category with no genes, a gene id in two categories or
an annotation field disagreeing with the file name are all faults in what was
committed here. A :class:ValueError, because a hand-curated file that says something
the format does not is a bad value rather than a broken program.
The message names the file and what is wrong with it, since fixing the file is the only thing anyone can do about it.
Examples:
GeneCategoryNotDeclaredError ¶
GeneCategoryNotDeclaredError(
annotation: str,
assembly: str,
category: str,
declared: Iterable[str],
)
Bases: LookupError
The annotation declares categories, and not the one asked for.
The second of the two absences. It is a real answer about a real curated list — the category was not curated for this annotation, which is not the same as its being curated and empty, and no shipped category is ever empty. The message lists the ones it does declare, since those are what a caller may ask for instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name that was asked about. |
required |
assembly
|
str
|
The Assembly it is registered for. |
required |
category
|
str
|
The category that is not declared. |
required |
declared
|
iterable of str
|
The categories that are, in the order the curated lists spell them. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
annotation |
str
|
The name asked about. |
assembly |
str
|
The assembly it is registered for. |
category |
str
|
The category nobody declared. |
declared |
tuple of str
|
The categories that are declared. |
Examples:
>>> try:
... raise GeneCategoryNotDeclaredError("mine", "tiny", "tRNA", ["rRNA"])
... except LookupError as error:
... print("rRNA" in str(error))
True
GeneList
dataclass
¶
The genes one annotation puts in one Gene category, attributed to their sources.
:meth:AnnotationRegistry.gene_list's answer — what genome annotation gene-list
prints and what its --json serializes. There is no empty one: an annotation that declares no
categories, and one that declares categories but not this one, each raise a
:class:LookupError of their own rather than answering with nothing, so holding one of
these means the category was really declared and really has genes in it.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly asked about. |
annotation |
str
|
The Registered name asked about — the merged name for a Merged annotation,
whose contributors are named in :attr: |
category |
str
|
The Gene category, as the curated lists spell it. |
sources |
tuple of GeneListSource
|
One entry per contributing Curated gene list, in contributor order. Never empty. A contributor that does not declare this category is simply absent — a bacterium has no mitochondria, and that is not a failure to report. |
Examples:
>>> genes = GeneList(
... assembly="ce11",
... annotation="wormbase_ws298",
... category="rRNA",
... sources=(
... GeneListSource(None, "wormbase_ws298", "rRNA genes", "WormBase", ("a", "b")),
... ),
... )
>>> genes.gene_ids
['a', 'b']
>>> genes.as_json()["category"]
'rRNA'
gene_ids
property
¶
Every source's gene ids, concatenated in source order — a fresh list each call.
Concatenated and not de-duplicated. A merge rewrites only the seqname and
never the gene_id, so two components carrying the same id would be a real
ambiguity in the merged annotation, and collapsing it here would hide exactly that
— a caller summing over these would silently under-count one of the two. Where
that matters, :attr:sources says which contributor each id came from.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
GeneListAssemblyMismatchError ¶
Bases: CuratedGeneListError
A curated list was asked for by an annotation registered against another assembly.
The list names the Assembly it was curated against, and that is checked rather than assumed: an annotation registered under a name whose curated list belongs to a different reference would otherwise answer with another species' gene ids, and mixing builds is an error and not a warning.
Not one of the two absences: nothing is missing, so a caller catching
:class:LookupError for absence does not swallow this.
Examples:
NoGeneCategoriesError ¶
NoGeneCategoriesError(
annotation: str,
assembly: str,
curated: Iterable[str],
*,
contributors: Iterable[str] = (),
)
Bases: LookupError
Nothing is known about this annotation's categories, so none can be asked of it.
The first of the two absences, and the one a caller must never read as this annotation has no rRNA genes: no curated list ships for it, so the question was not answered rather than answered with nothing. The message says which annotations do declare categories and that answering for this one means shipping a curated list.
A :class:LookupError, so a caller may catch it together with
:class:GeneCategoryNotDeclaredError and still act differently on each.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name that was asked about — the merged name for a Merged annotation. |
required |
assembly
|
str
|
The Assembly it is registered for. |
required |
curated
|
iterable of str
|
The annotations a curated list does ship for. |
required |
contributors
|
iterable of str
|
For a Merged annotation, the annotations it merges. Shipping a list under the merged name would fix nothing; it is these that need one. |
()
|
Attributes:
| Name | Type | Description |
|---|---|---|
annotation |
str
|
The name asked about. |
assembly |
str
|
The assembly it is registered for. |
curated |
tuple of str
|
The annotations that do ship a curated list. |
contributors |
tuple of str
|
The contributing annotations, empty for anything but a merge. |
Examples:
>>> try:
... raise NoGeneCategoriesError("mine", "tiny", ["gencode_v50"])
... except LookupError as error:
... print("gencode_v50" in str(error))
True
AmbiguousDefaultAnnotationError ¶
Bases: ValueError
A component carries several annotations and nothing says which one a chimera takes.
A Component contributes its own Default annotation, and a component with several registered and none flagged by the annotation table has no default at all — which is the ordinary, deliberate answer to pick one for me everywhere else in the package, and the one place it cannot stand. Guessing would put a set of gene models into a merged annotation nobody chose, under a name that would look identical to the one the caller meant.
The message names default_gtf=, which is how a caller says which, and it says so
of that component rather than of the chimera: the fix is one argument on one
component's constructor.
A :class:ValueError because the component handed in is not one this build can use.
Examples:
Genome ¶
Genome(
assembly: str,
*,
path_or_url: str | Path | None = None,
cache_dir: str | Path | None = None,
progressbar: bool = True,
metadata: AssemblyMetadata | None = None,
default_gtf: str | None = None,
)
Bases: AlignerMixin, MotifScanMixin
A reference genome and the operations over it.
Constructing a Genome ensures the assembly's reference files exist
locally: the FASTA is fetched from the Source its metadata row pins —
UCSC for most assemblies, WormBase or NCBI for others — or, for an assembly
no row lists, from a URL derived from the UCSC golden path. Its .fai
index, .2bit encoding and chrom.sizes are then prepared, and a
completion record is written recording what was done. Everything lands under
<LIULAB_DATA>/genome/<assembly>/ (see
:func:~genome.assembly.download.assembly_data_dir). A later construction reads
that record, confirms every file it claims is present at the size it claims,
and opens the .2bit — so it is instant and works offline. Nothing is
downloaded twice.
Sequence is read from the .2bit file via py2bit; coordinates are
0-based, half-open throughout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
Assembly name, e.g. |
required |
path_or_url
|
str or Path
|
Seed the assembly from your own FASTA instead of downloading from UCSC —
either a local file path (copied into the cache) or an http(s)/ftp/sftp
URL (fetched with pooch). Gzipped ( |
None
|
cache_dir
|
str or Path
|
Override the storage directory for this assembly's files. Defaults to the shared per-assembly reference directory. |
None
|
progressbar
|
bool
|
Show a download progress bar on first fetch (requires |
True
|
metadata
|
AssemblyMetadata
|
A complete metadata record, used instead of the curated table's row for
|
None
|
default_gtf
|
str
|
Name of the annotation to serve as :attr: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly name. |
files |
GenomeFiles
|
Paths to the prepared FASTA and its derived index/companion files. |
metadata |
AssemblyMetadata
|
The assembly's metadata record, and always a record: the one passed in,
else the curated table's row, else — for an assembly the table does not
list — one carrying the name with every identifier unknown. Read a field
off it ( |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RegistrationError
|
If the assembly's directory holds a registration that cannot be trusted —
files with no record (an interrupted run), or a record that disagrees with
what is on disk. The message names the file and
|
ToolNotFoundError
|
If a required native tool ( |
Examples:
default_gtf
property
¶
Name of this genome's Default annotation, or None when there is none.
The default_gtf argument if one was given, else the annotation the table flags
for this assembly, else the sole registered annotation, else None. It names an
annotation that may not be registered here, which on a fresh machine is the normal
state and not an error — :attr:default_gtf_path is where it has to exist.
annotations
property
¶
The assembly's annotations: what is registered, broken, offered, or nothing.
The whole collection surface behind one name. Every question about several
annotations is asked of the registry — .registered for what is on this
machine, .broken for what is here and cannot be trusted, .offered for
what the lab supports, .path(name) for where one is, .register(name) and
.register_path(gtf, name) for the two acts that add one. The everyday read is
the default annotation, which :attr:default_gtf and :attr:default_gtf_path
answer directly.
It is deliberately not a list: a registry settles a four-way state, and
iterating or measuring it would silently privilege one of the four. There is no
len(), no in, and no iteration — name the set you mean.
Examples:
default_gtf_path
property
¶
GTF file path of the Default annotation, or None when there is no default.
Where the default stops being an intention and has to exist. :attr:default_gtf
may name an annotation nobody has registered on this machine — the table's
choice, on a machine that has not fetched it yet, or one a caller named at
construction ahead of registering it — and asking for its path is what says so,
naming the command that registers it, or the one that repairs it when a broken
directory of that name is there. None means no default was decided at all,
which is a different answer from one that is not registered.
Raises:
| Type | Description |
|---|---|
AnnotationNotRegisteredError
|
If the default annotation is not registered here. |
Examples:
assembly_dir
property
¶
The Assembly dir this genome was opened in, and the layout inside it.
Where everything tied to this assembly lives — its own files, the gtf/
subtree its annotations are filed under, the index/ subtree its indexes are
built into. Public because it is what an Index is derived from: an index
belongs inside the assembly it indexes, and asking the genome is the only way to
get the directory this genome was actually opened in rather than the one the
Data dir layout would name for its assembly.
Examples:
chrom_sizes_path
property
¶
Path to the chrom.sizes file (<name>\t<length> per sequence).
chrom_sizes
property
¶
Chromosome lengths as a pandas Series (a defensive copy).
Integer lengths indexed by chromosome name, in reference order.
chromosomes
property
¶
Chromosome names, in the order the reference declares them.
components
property
¶
The Component assembly names this is a Chimera of, or None.
The single test of whether an assembly is a chimera, and the completion record is what answers it — never the metadata table, which lists a chimera as a cross-reference and would answer the same question differently on a machine where the row is stale or absent.
Sorted, which for a chimera is the order its own derived name spells them in.
None — not an empty list — for an ordinary assembly: it is not a chimera of
nothing, it is not a chimera.
Examples:
separator
property
¶
The underscore run this Chimera's chromosome names are suffixed with, or None.
Read off the completion record, which is the only honest answer: the separator
belongs to one chimera, since a component whose own names carry __ forces a
longer run. A caller that assumed the default would split
NZ_TINY02__000002.1___tinyEcDub in the wrong place and hand the chromosome to
the wrong component. None — not the default — for an assembly that is not a
chimera, mirroring :attr:components.
Hand it to :func:~genome.assembly.chimera.split_suffixed to read a name back.
Examples:
component_annotations
property
¶
What each Component contributed to the Merged annotation, or None.
The Registered name per component, in the sorted-component order
:attr:components uses, and None for a component that contributed nothing —
a distinction that decides which annotation a per-component count is taken
against, so it is read off the record rather than from
Genome(component).default_gtf: a component's default now is not necessarily
what went into this merge. None — not an empty mapping — for an assembly that
is not a chimera, mirroring :attr:components.
Examples:
chrom_components
property
¶
Which assembly each chromosome came from, as a Series mirroring :attr:chrom_sizes.
Attribution, and total: every chromosome this reference carries gets an
answer. For a chimera each name is split at its recorded separator, so the answer
is read out of the name itself and no mapping was ever stored; for an
assembly that is not a chimera every chromosome maps to that assembly's own name,
which is true rather than merely convenient — and it is what leaves
:attr:components as the single is-chimera test, since no caller has to read
this one to find out.
Returns:
| Type | Description |
|---|---|
Series
|
Component assembly name per chromosome, indexed and ordered exactly as
:attr: |
Examples:
chimera
classmethod
¶
Build a Chimera of components and return it open, like any other genome.
A second constructor rather than a second type: the reference it
produces is an assembly, so everything an assembly can do — fetch sequence,
register an annotation, build an index — it can do, by one code path and not two.
Its FASTA is its components' bytes with every chromosome name suffixed by the
component it came from; see :mod:genome.assembly.chimera_build for how it is
written.
The name is derived, never given — the component names sorted and joined by
_, so ce11 and ecHT115 in either order build and reopen the one
ce11_ecHT115. A chimera whose record says it finished is opened without
rewriting anything; a directory that cannot be trusted raises and names
genome assembly register <name> --force, which is what force is.
Nothing is fetched, and no annotation argument is needed or accepted: each
component carries its own :attr:default_gtf, so a caller's (assembly, gtf)
pairs split at the door and the annotation half travels with the components. Those
defaults are merged in the same act, registered under the +-join of their
names in sorted-component order — so a built chimera arrives annotated and
force repairs the annotation and the FASTA together. A component with no
annotation contributes nothing, and components that contribute nothing between
them leave the chimera with no annotation rather than an empty one; a component
whose default is named but not registered here, or which has several registered
and no default, raises before anything is written.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*components
|
Genome
|
Two or more prepared component assemblies, in any order, each given once. None may itself be a chimera — a Component is always a canonical assembly, so nesting is forbidden by the model rather than deferred. |
()
|
cache_dir
|
str or Path
|
Override the directory the chimera is built and opened in, exactly as the
constructor's |
None
|
force
|
bool
|
Build again from scratch — the repair for a directory that raises. |
False
|
Returns:
| Type | Description |
|---|---|
Genome
|
The chimera, opened under its derived name. |
Raises:
| Type | Description |
|---|---|
ChimeraNamingError
|
If fewer than two components are given, a component repeats, a component's name is not alphanumeric, a component is itself a chimera, or a component's FASTA carries a header that names no sequence for the suffix to ride on. |
AnnotationNotRegisteredError
|
If a component's default annotation is named but not registered here; the message names the command that registers it. |
AmbiguousDefaultAnnotationError
|
If a component carries several annotations and none is its default; the
message names |
RegistrationError
|
If the chimera's directory holds a build that cannot be trusted as finished, or the FASTA just built does not carry the sequences its components predict. |
ChromosomeMismatchError
|
If the merged annotation names a sequence the built FASTA does not carry. |
ToolNotFoundError
|
If |
Examples:
gene_list ¶
Return the genes one of this genome's annotations puts in category.
The everyday way in to a Gene category: name the category and get the gene ids
in it, with one source per contributing Curated gene list so a Chimera's
genes stay attributable to the component they came from. Which categories exist is
a property of the annotation rather than of this package — ask
:meth:gene_lists what this one declares.
Nothing here answers with an empty collection. An annotation no curated list ships for and one whose list does not declare this category raise different errors, because a caller acts differently on nothing can be asked of this annotation and this category was not curated for it — and neither means the annotation has no such genes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
The Gene category, as the curated list spells it — |
required |
annotation
|
str
|
The Registered name to ask about. Omitted, :attr: |
None
|
Returns:
| Type | Description |
|---|---|
GeneList
|
The category, its gene ids, and what contributed them. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneCategoriesError
|
If no curated gene list ships for it. |
GeneCategoryNotDeclaredError
|
If it declares categories and not this one. |
Examples:
gene_lists ¶
Return every Gene category one of this genome's annotations declares.
:meth:gene_list for all of them at once, in declaration order — the way to find
out what may be asked for, since the categories are the curated list's to declare
and differ between annotations. Never an empty tuple: an annotation that declares
none raises instead, so declares nothing is never read as declares empty
categories.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name to ask about. Omitted, :attr: |
None
|
Returns:
| Type | Description |
|---|---|
tuple of genome.annotation.registry.GeneList
|
One entry per declared category. Never empty. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneCategoriesError
|
If no curated gene list ships for it. |
Examples:
tf_gene_list ¶
Return the genes a published census judges transcription factors in this genome.
The everyday way to a TF gene list: the census shipped for this assembly's own species, resolved into one registered annotation's gene ids, so the answer joins to a counts matrix with nothing left to normalise. Assessed-positive by default, with the census's DBD family and every judgement it recorded on each gene, and the provenance that says whose verdict it is — nothing here decides what a transcription factor is.
The species is this assembly's, read from its metadata row and never passed in, so asking for another species' factors is not expressible. An assembly whose species has no census, and one nothing names a species for, raise rather than answering with nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name to answer in the gene ids of. Omitted,
:attr: |
None
|
include_rejected
|
bool
|
Carry the genes the census assessed and turned down as well, each saying so. |
False
|
Returns:
| Type | Description |
|---|---|
TFGeneList
|
The genes, the census's provenance, and the stems that resolved to nothing. |
Raises:
| Type | Description |
|---|---|
UnknownSpeciesError
|
If nothing names this assembly's species. |
NoTFCensusError
|
If no census ships for that species; the message names the ones that do. |
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneFeaturesError
|
If its database holds no gene at all. |
Examples:
tf_cofactor_list ¶
Return the genes a publisher lists as transcription cofactors in this genome.
The everyday way to a TF cofactor list, and the counterpart of
:meth:tf_gene_list in the same shape: the Cofactor table shipped for this
assembly's own species, resolved into one registered annotation's gene ids, so the
answer joins to a counts matrix with nothing left to normalise. Each entry carries
which publisher listed the gene and that publisher's own classification of it —
membership is this package's and classification is theirs.
A cofactor recognises no sequence of its own and so has no motif; this says which genes are cofactors, not what they bind, and nothing here ranks one above another.
The species is this assembly's, read from its metadata row and never passed in.
An assembly whose species has no cofactor table, and one nothing names a species
for, raise rather than answering with nothing. Worm has a table although no TF
census covers it, so a worm genome answers here and raises from
:meth:tf_gene_list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name to answer in the gene ids of. Omitted,
:attr: |
None
|
Returns:
| Type | Description |
|---|---|
TFCofactorList
|
The cofactors, the publishers' provenance, and the stems that resolved to nothing. |
Raises:
| Type | Description |
|---|---|
UnknownSpeciesError
|
If nothing names this assembly's species. |
NoCofactorTableError
|
If no cofactor table ships for that species; the message names the ones that do. |
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneFeaturesError
|
If its database holds no gene at all. |
Examples:
__exit__ ¶
__exit__(
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None
Close the 2bit handle on context-manager exit.
fetch_sequence ¶
Return the reference sequence for region as a :class:~genome.seq.DNA.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
region
|
str or Region
|
Either a locus string |
required |
Returns:
| Type | Description |
|---|---|
DNA
|
The sequence, with soft-masking case preserved. May contain |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
__getitem__ ¶
Index by locus string or :class:~genome.region.Region — sugar for :meth:fetch_sequence.
AssemblyMetadata
dataclass
¶
AssemblyMetadata(
assembly_name: str,
species: str | None,
ucsc_name: str | None,
ncbi_name: str | None,
ncbi_assembly_id: str | None,
ncbi_taxid: int | None,
source_url: str | None = None,
sha256: str | None = None,
intron_length_cap: int | None = None,
intron_length_cap_rationale: str | None = None,
)
Identifiers for one reference assembly (one row of the metadata table).
The single declaration of what an assembly's metadata consists of: the table
is parsed through these fields, and a complete record is what
:class:~genome.assembly.genome.Genome accepts in place of the table's own row.
Only assembly_name is required. Every other column may be left blank, and a
blank cell reads back as None rather than as text: the table fills in over
time, and a freshly prepared assembly pins its source and digest well before
anyone supplies its species, its UCSC and NCBI names or its taxonomy id.
source_url and sha256 are what makes preparing an assembly reproducible.
source_url pins where its FASTA is fetched from, so nothing has to be derived
or guessed; sha256 pins the digest of the unpacked FASTA that source
yields — not of the compressed archive it arrives in, so a copy taken from a
mirror or recompressed elsewhere still matches. A row with no digest is
unverified rather than wrong.
intron_length_cap is the longest gap a spliced aligner should take for an
intron on this assembly, and intron_length_cap_rationale says why that number
and not another. It is a deliberately loose round number set by hand, never
computed from an annotation — an annotation catalogues the transcripts someone
observed, so its longest intron is a floor on what the organism does rather than a
ceiling on it. Nothing in this package reads either field: they are
curated here so that a consumer choosing aligner parameters reads a fact about the
assembly from the same row as its identifiers. A blank cap is an assembly nobody
has characterised, which is legal and says no bound has been chosen — the reading
that leaves such an assembly aligning exactly as it did before.
A Chimera's row pins neither, and that is deliberate rather than pending: its
bytes are not fetched from anywhere, and they are derived by a pure function from
components whose own rows are pinned, so it is proven transitively. The table pins
what was downloaded; what was derived is pinned by a test, where a change in this
package's own concatenation code belongs. Such a row carries its name and
nothing else — its identifiers are its components', reachable through
:attr:~genome.assembly.genome.Genome.chrom_components — and it exists so that a machine
holding none of them can still tell a chimera's name from a local key someone chose.
ucsc_name is blank permanently rather than pending in some rows, for the same
reason the assembly id is a local key rather than a UCSC one: the lab
supports references UCSC has never carried, and such a row simply has no name in
that namespace to give.
Examples:
>>> record = AssemblyMetadata(
... "hg38", "Homo sapiens", "hg38", "GRCh38", "GCF_000001405.40", 9606
... )
>>> record.species
'Homo sapiens'
>>> record.sha256 is None # nothing pinned unless the row says so
True
unknown
classmethod
¶
Return the record for an assembly the table does not list: the name, and nothing else.
What unlisted looks like as a record rather than as a missing one. Every
identifier is genuinely unknown, which a blank cell already means everywhere
else, so a caller reads a field and gets None instead of first asking
whether there is a record to read it off. It is exactly the line
:func:format_table_row emits for an assembly nobody has curated yet.
The name is the local key the caller asked for, since that is the only
identifier an unlisted assembly has — the assembly id is a local key and the
table is a cross-reference rather than an allow-list. This answers
what is known about this assembly; whether the table lists it at all is
:func:lookup_assembly's question and stays a separate one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_name
|
str
|
The assembly the record is for. |
required |
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
A record carrying |
Examples:
from_row
classmethod
¶
Build a record from one row of a metadata table.
The reader half of the register-then-paste flow :func:format_table_row writes:
a row is a mapping of column name to cell, which is how the shipped TSV is read
and how :func:dataclasses.asdict of a record spells one. Each column is parsed
by its own declared type, and a blank cell — empty, absent, or the NaN pandas
reads a blank as — means unknown, which only a column that has an unknown takes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Column name to cell. A cell is the table's own text, but a value already of
the column's type is taken as it stands, so a record's fields are a row.
Keys outside :data: |
required |
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
The record the row spells. |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
If a cell cannot be read as its column's type, or a column that has no unknown is blank. The record is built from parsed cells or not at all, so a caller is handed a whole record or an error naming the column — never a record carrying the columns that happened to come before the bad one. |
Examples:
ToolNotFoundError ¶
Bases: RuntimeError
Raised when an External tool cannot be located on PATH.
The message is the tool's :meth:ExternalTool.install_instructions, so the next
action is in the exception rather than somewhere the caller has to go and look.
Examples:
Region
dataclass
¶
A single genomic interval in 0-based, half-open coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chrom
|
str
|
Chromosome / sequence name. |
required |
start
|
int
|
0-based start, inclusive. Must be |
required |
end
|
int
|
0-based end, exclusive (half-open). Must be |
required |
strand
|
str
|
|
``"."``
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> r = Region("chr1", 0, 10)
>>> len(r), r.length
(10, 10)
>>> Region.from_string("chr2:100-200", strand="-")
Region(chrom='chr2', start=100, end=200, strand='-')
from_string
classmethod
¶
Build a :class:Region from a 0-based chrom:start-end string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
A |
required |
strand
|
str
|
Strand to attach (the string itself carries no strand). |
``"."``
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
DNA ¶
Bases: _Seq
A DNA sequence over the four canonical bases.
Alphabet: A, C, G, T (case-insensitive at construction;
case preserved in the stored value).
Examples:
>>> DNA("ATCG")
DNA('ATCG')
>>> DNA("aTcG")[1:3] # slicing stays typed
DNA('Tc')
>>> DNA("ATCG").reverse_complement()
DNA('CGAT')
>>> DNA("ATCG").transcribe()
RNA('AUCG')
>>> DNA("GGCC").gc_content
1.0
RNA ¶
MetadataRowError ¶
Bases: ShippedTableError
A row cannot be read as a record, and the message names the column that refused.
Raised by :func:parse_cell for the curated tables that declare their columns as a frozen
dataclass — the Assembly metadata and Annotation metadata tables and the Xref
source table — for a cell no column's type can read and for a blank cell in a column that
has no unknown. Re-exported from :mod:genome.assembly.metadata, which is where those tables live.
Examples:
ShippedTableError ¶
Bases: ValueError
A Shipped table cannot be read, so it is not allowed to answer.
The base of every table's own error class, so a caller may catch one kind of defect across
all of them and still tell a census from a Cofactor table by the class that was raised.
A :class:ValueError, because a file that says something the format does not is a bad value
rather than a broken program — and never a :class:LookupError, which is what absence is
spelled with, so catching an absent table cannot swallow a broken one.
Examples:
NoCofactorTableError ¶
Bases: LookupError
Nobody has listed this Assembly's species' cofactors, so no table can answer.
:class:NoTFCensusError's counterpart on the cofactor half, and the same kind of
fact: nobody has published a cofactor list for this species, which is about the
literature and not about the genome, and must never be read as this species has no
transcription cofactors. Yeast and E. coli land here today.
Worm does not, although :class:NoTFCensusError raises for the same assembly. A
publisher assessed worm cofactors and none has released a worm TF census, so the two
halves answer differently for one species — the publishers' shape, and not a defect.
A :class:LookupError, so it may be caught together with :class:UnknownSpeciesError
and still told apart, exactly as the census pair is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The Assembly asked about. |
required |
species
|
str
|
The species its metadata row names, which is what no table ships for. |
required |
shipped_for
|
iterable of str
|
The species a Cofactor table does ship for. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly asked about. |
species |
str
|
Its species. |
shipped_for |
tuple of str
|
The species that do have a cofactor table. |
Examples:
>>> try:
... raise NoCofactorTableError("sacCer3", "Saccharomyces cerevisiae", ["Mus musculus"])
... except LookupError as error:
... print("Mus musculus" in str(error))
True
TFCofactorList
dataclass
¶
TFCofactorList(
assembly: str,
annotation: str,
species: str,
provenance: CofactorProvenance,
cofactors: tuple[TFCofactor, ...],
unresolved: tuple[str, ...],
)
One Assembly's Transcription cofactors, in its annotation's own gene ids.
:func:resolve_tf_cofactors's answer, and the counterpart of
:class:~genome.tf.gene.annotation.TFGeneList in the same shape: the Cofactor
table's Gene id stems resolved against one registered annotation, so the ids
join to a counts matrix with nothing left to normalise.
Membership is this package's and classification is each publisher's. A table built
from two publishers is a union nobody else published, which is why :attr:provenance
carries a record per publisher rather than one, and why a source of both on an
entry says the two agreed the gene is a cofactor and nothing about how either
classified it.
What the table holds and this annotation does not is visible. A stem no gene id
here is of comes back in :attr:unresolved rather than being dropped.
There is no empty one, for the reasons an absent table would give: an assembly whose
species has no cofactor table, and one nothing names a species for, each raise a
:class:LookupError of their own.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly asked about. |
annotation |
str
|
The Registered name whose own gene ids these are. |
species |
str
|
The species the assembly's own metadata row names, which is what selected the table. Never passed in by a caller, so asking for one species' cofactors while holding another species' assembly is not expressible. |
provenance |
CofactorProvenance
|
Where the table came from: one record per publisher that contributed to it, plus
the digest of the shipped bytes.
:meth: |
cofactors |
tuple of TFCofactor
|
One entry per Gene id stem that named at least one gene id here, in the table's own row order. |
unresolved |
tuple of str
|
The stems this annotation carries no gene for, in table row order. |
Examples:
>>> from genome.tf.cofactor import cofactor_table
>>> answer = TFCofactorList(
... assembly="mm39",
... annotation="gencode_vM39",
... species="Mus musculus",
... provenance=cofactor_table("Mus musculus").provenance,
... cofactors=(
... TFCofactor(
... "ENSMUSG00000000085",
... ("ENSMUSG00000000085.16",),
... "Scmh1",
... True,
... "animaltfdb",
... {},
... ),
... ),
... unresolved=("ENSMUSG00000000275",),
... )
>>> answer.gene_ids
['ENSMUSG00000000085.16']
>>> answer.provenance.sources[0].publisher
'AnimalTFDB'
>>> answer.as_json()["unresolved"]
['ENSMUSG00000000275']
gene_ids
property
¶
Every gene id, cofactor order then id order — a fresh list each call.
Every id, not one per gene, for the reason
:attr:~genome.tf.gene.annotation.TFGeneList.gene_ids gives: flattening is
where a reader would take the first id of a stem that names two and lose the
other. :attr:cofactors is what says which gene an id came from, and what the
publisher said about it.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
NoTFCensusError ¶
Bases: LookupError
No census has been published for this Assembly's species, so none can answer.
The first of the two absences a TF gene list has, and the one a caller must never read as this species has no transcription factors: nobody has published a census for it, which is a fact about the literature and not about the genome. Worm, yeast and E. coli land here today. The message names the species that do have one, since asking about one of those is the thing a caller can do instead.
A :class:LookupError, so it may be caught together with :class:UnknownSpeciesError
and still told apart — exactly as the Curated gene list's two absences are.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The Assembly asked about. |
required |
species
|
str
|
The species its metadata row names, which is what no census ships for. |
required |
censused
|
iterable of str
|
The species a census does ship for. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly asked about. |
species |
str
|
Its species. |
censused |
tuple of str
|
The species that do have a census. |
Examples:
>>> try:
... raise NoTFCensusError("ce11", "Caenorhabditis elegans", ["Homo sapiens"])
... except LookupError as error:
... print("Homo sapiens" in str(error))
True
TFGeneList
dataclass
¶
TFGeneList(
assembly: str,
annotation: str,
species: str,
provenance: CensusProvenance,
genes: tuple[TFGene, ...],
unresolved: tuple[str, ...],
)
One Assembly's TF genes, in its registered annotation's own gene ids.
:func:resolve_tf_genes's answer, and what a --json surface over it serializes.
The census's Gene id stems resolved against one annotation, so the ids join to a
counts matrix with nothing left to normalise, and assessed-positive by default: the
common case is not 2,765 rows to filter down to 1,639.
Nothing here decides what a transcription factor is. Every verdict is the census's
and travels with :attr:provenance, which names the publisher to cite. Two censuses
that classify one factor differently are two answers rather than a contradiction, and
this says which one is speaking.
What the census holds and this annotation does not is visible. A stem no gene id
here is of comes back in :attr:unresolved rather than being dropped, so a caller can
count what the crossing cost instead of wondering.
There is no empty one for the reasons an absent census would give: an assembly whose
species has no census, and one nothing names a species for, each raise a
:class:LookupError of their own.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly asked about. |
annotation |
str
|
The Registered name whose own gene ids these are. |
species |
str
|
The species the assembly's own metadata row names, which is what selected the census. Never passed in by a caller, so asking for one species' transcription factors while holding another species' assembly is not expressible. |
provenance |
CensusProvenance
|
Where the census came from: publisher, version, PubMed id, source URL and digest.
:meth: |
genes |
tuple of TFGene
|
One entry per Gene id stem that named at least one gene id here, in the census's own row order. |
unresolved |
tuple of str
|
The stems this annotation carries no gene for, in census row order. |
Examples:
>>> from genome.tf.gene import tf_gene_table
>>> answer = TFGeneList(
... assembly="hg38",
... annotation="gencode_v50",
... species="Homo sapiens",
... provenance=tf_gene_table("Homo sapiens").provenance,
... genes=(
... TFGene("ENSG00000137203", ("ENSG00000137203.12",), "TFAP2A", True, "AP-2", {}),
... ),
... unresolved=("ENSG00000214717",),
... )
>>> answer.gene_ids
['ENSG00000137203.12']
>>> answer.provenance.publisher
'Lambert et al. 2018'
>>> answer.as_json()["unresolved"]
['ENSG00000214717']
gene_ids
property
¶
Every gene id, gene order then id order — a fresh list each call.
Every id, not one per gene, for the reason
:attr:~genome.annotation.stems.ResolvedGeneIds.gene_ids gives: flattening is where a
reader would take the first id of a stem that names two and lose the other.
:attr:genes is what says which gene an id came from, and what the census said
about it.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
UnknownSpeciesError ¶
Bases: LookupError
Nothing says what species this Assembly is, so no shipped table can be chosen.
The second absence both gene-keyed halves have, and a different fact from
:class:NoTFCensusError and :class:NoCofactorTableError: the question was not has
anyone published for this species but which species is this, and nothing answered
it. Two ways in, and neither is a mistake — a Chimera is more than one species by
construction and nothing published answers for one, and an assembly the curated table
does not list carries no species at all, which is the ordinary state of a free-form
local key.
The species is read from the assembly's own metadata and never passed in, so this is what a caller gets instead of quietly being handed another species' answer.
One class for both halves because the fact is one fact: only what a caller can ask
about instead differs, and shipped_table is what says which of them was asked for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The Assembly whose species nothing names. |
required |
shipped_for
|
iterable of str
|
The species the table asked for does ship for. |
required |
shipped_table
|
str
|
What could not be chosen, named in the message — |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly asked about. |
shipped_for |
tuple of str
|
The species the table asked for does ship for. |
shipped_table |
str
|
What could not be chosen. |
Examples:
>>> try:
... raise UnknownSpeciesError(
... "ce11_ecHT115", ["Homo sapiens"], shipped_table="TF census"
... )
... except LookupError as error:
... print("Homo sapiens" in str(error))
True
genome.aligner ¶
Aligner abstractions and the :class:AlignerMixin for :class:~genome.assembly.genome.Genome.
Aligner ¶
Bases: ABC
Base class for an external aligner that can build a genome index.
Subclasses set the class attributes :attr:name (the lowercase identifier
used in the index path), :attr:binary (the executable on PATH) and
:attr:_flag_separator (how its long options join words), and implement
:meth:index, :attr:_artifact and :attr:_build_arguments. An :meth:index
hands :meth:_build a way to compose its command line; :meth:_build owns the
sequence every build shares and composes only when a build is going to run.
An aligner is given its External tool rather than making one, and constructing
it runs nothing at all: the binary is located, and its version asked for, the first
time either is needed. A binary that is not installed raises
:class:~genome.external.ToolNotFoundError carrying the install instructions, at the
point the build would have started rather than at construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
genome
|
Genome
|
The genome whose reference FASTA will be indexed. |
required |
tool
|
ExternalTool
|
The tool to drive. Defaults to :attr: |
None
|
index_dir
property
¶
Directory holding this aligner's index for the assembly.
<assembly dir>/index/<name>/ — inside the Assembly dir the bound genome
was opened in, asked of that genome rather than re-derived from the Data dir.
The two agree under the ordinary layout and part company for a genome opened
somewhere of its own, where re-deriving would put the index beside a different
assembly's files and read a completion record that is not there.
index_path
property
¶
The built index file or prefix this aligner consumes.
The exact flavour (a directory, a file, or a path prefix) is decided by
the subclass via :attr:_artifact. Reading this property asserts that
the build finished, and the completion record in :attr:index_dir is
what says so — never the presence of an index file, which is exactly what
that record exists to distrust.
Returns:
| Type | Description |
|---|---|
Path
|
The path to hand to the aligner's own command line. |
Raises:
| Type | Description |
|---|---|
IndexNotBuiltError
|
If nothing has been built here yet. |
UnfinishedRegistrationError
|
If the directory holds index files but no record — a build that was interrupted before it finished. |
RegistrationMismatchError
|
If the record disagrees with what is on disk, naming every file that differs and how, or if the assembly was re-registered after this index was built, naming both digests. |
install_instructions ¶
Return how to install this aligner — its tool's own text.
Every aligner owes one, and it is the same text the
:class:~genome.external.ToolNotFoundError carries, so a caller reading either
gets the command to run.
Returns:
| Type | Description |
|---|---|
str
|
What to run, naming the bioconda package. |
Examples:
index
abstractmethod
¶
Build the genome index and return :attr:index_path.
IndexNotBuiltError ¶
Bases: RuntimeError
No index has ever been built where one was asked for.
Distinct from the two states :mod:genome.store.completion calls broken: nothing
on disk is damaged or half-written, there is simply no index there yet. The
message names the call that builds one, so the gap is self-explaining.
Chromap ¶
Bases: Aligner
Chromap aligner index builder.
A chromap index is a single minimizer table built from the reference FASTA
alone; it carries no annotation, so — unlike
:class:~genome.aligner.star.STAR — there is exactly one index per assembly.
The index is written to .../index/chromap/chromap.index and
:attr:index_path returns that file (chromap consumes it via -x/--index).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
genome
|
Genome
|
The genome whose reference FASTA will be indexed. |
required |
tool
|
ExternalTool
|
As :class: |
None
|
index ¶
index(
*,
kmer: int | None = None,
window: int | None = None,
overwrite: bool = False,
**kwargs: Any,
) -> Path
Build the chromap index for the bound assembly and return :attr:index_path.
Output goes to <LIULAB_DATA>/genome/<assembly>/index/chromap/chromap.index.
An index whose completion record says it finished is reused unless
overwrite=True; a directory holding index files that no record vouches for
raises rather than being silently rebuilt. chromap needs only the reference
FASTA — no gene annotation — so one index serves every use of the assembly.
Only the two minimizer knobs are named below. Any other --build-index
option may be passed as a keyword argument using chromap's flag name with
underscores for hyphens (e.g. min_frag_length=30 -> --min-frag-length
30); for their meaning see chromap --help.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kmer
|
int
|
|
None
|
window
|
int
|
|
None
|
overwrite
|
bool
|
Rebuild even if a finished index already exists, and rebuild over a directory that cannot be trusted rather than raising on it. |
False
|
**kwargs
|
Any
|
Extra |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The built index file (also available as :attr: |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the index directory holds files without a record, a record that
disagrees with them, or a record pinning a different assembly digest
than the one registered now. Pass |
RuntimeError
|
If chromap exits non-zero. |
AlignerMixin ¶
Build aligner genome indexes for a :class:~genome.assembly.genome.Genome.
Each build_<aligner>_index method instantiates the corresponding
:class:~genome.aligner.aligner.Aligner. Constructing one runs nothing: the
binary is located and asked its version on first use, so a missing aligner
raises when a build is asked for and not before. Index files land under
<assembly dir>/index/<aligner>/ — inside the Assembly dir the genome
was opened in.
build_star_index ¶
build_star_index(
gtf: str | None = None,
*,
tool: ExternalTool | None = None,
**kwargs: Any,
) -> Path
Build a STAR genome index for this assembly against annotation gtf.
A thin entry point onto :meth:genome.aligner.star.STAR.index; the
remaining keyword arguments are forwarded there (see its docstring for
the exposed options and how to pass arbitrary STAR flags).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gtf
|
str
|
Name of a GTF annotation registered on this genome (see
:meth: |
None
|
tool
|
ExternalTool
|
The tool to drive, forwarded to the aligner — the same seam
:class: |
None
|
**kwargs
|
Any
|
Forwarded to :meth: |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The built STAR genome directory. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
build_chromap_index ¶
Build a chromap genome index for this assembly.
A thin entry point onto :meth:genome.aligner.chromap.Chromap.index; the
keyword arguments are forwarded there (see its docstring for the exposed
options and how to pass arbitrary chromap flags). Unlike
:meth:build_star_index, chromap needs no gene annotation at all — not even a
default one — so there is no gtf argument and one index serves the whole
assembly. The index is written to index/chromap/chromap.index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool
|
ExternalTool
|
As :meth: |
None
|
**kwargs
|
Any
|
Forwarded to :meth: |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The built chromap index file. |
get_index ¶
Return the path of an already-built index, for use in aligner commands.
Locates the index a prior build_<aligner>_index produced and returns
the file or prefix to hand to the aligner's own command line (e.g. STAR's
--genomeDir). Nothing is built here: the index's completion record is
read, and anything short of a finished index that agrees with what is on
disk raises, naming the call that puts it right.
The aligner-specific selectors that identify which index are passed as
keyword arguments, mirroring the matching build_* method — for STAR,
the annotation gtf key that named the index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
aligner
|
str
|
Aligner identifier, case-insensitive (e.g. |
required |
tool
|
ExternalTool
|
As :meth: |
None
|
**kwargs
|
Any
|
Aligner-specific selectors forwarded to the aligner constructor to
pin down the index. STAR requires |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The built index file or prefix, ready to drop into the aligner command. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
IndexNotBuiltError
|
If no index has been built yet — build it first with the
corresponding |
RegistrationError
|
If the index directory holds files without a completion record, or a
record that disagrees with them; rebuild it with |
get_star_index ¶
Return the path of the STAR genomeDir built for annotation gtf.
Convenience wrapper over :meth:get_index for STAR — the returned
directory is what STAR's --genomeDir expects at mapping time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gtf
|
str
|
Name of the GTF annotation the index was built against (the same key
passed to :meth: |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The STAR genome directory. |
Raises:
| Type | Description |
|---|---|
IndexNotBuiltError
|
If no STAR index has been built yet for |
RegistrationError
|
If that index directory cannot be trusted; see :meth: |
get_chromap_index ¶
Return the path of the chromap index built for this assembly.
Convenience wrapper over :meth:get_index for chromap — the returned file
is what chromap's -x/--index expects at mapping time. A chromap index
carries no annotation, so no selector is needed.
Returns:
| Type | Description |
|---|---|
Path
|
The chromap index file. |
Raises:
| Type | Description |
|---|---|
IndexNotBuiltError
|
If no chromap index has been built yet for this assembly — build it
first with :meth: |
RegistrationError
|
If that index directory cannot be trusted; see :meth: |
STAR ¶
Bases: Aligner
STAR aligner index builder.
A STAR index is splice-junction-aware: it is built against one gene
annotation, so each annotation gets its own genomeDir. The bound GTF key
selects that annotation (its path resolved via
:meth:~genome.annotation.registry.AnnotationRegistry.path) and names the index directory
star_<gtf_key>. STAR's index is the genomeDir directory itself, so
:attr:index_path returns :attr:index_dir.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
genome
|
Genome
|
The genome whose reference FASTA will be indexed. |
required |
gtf
|
str
|
Name of a GTF annotation registered on |
required |
tool
|
ExternalTool
|
As :class: |
None
|
index ¶
index(
*,
sjdb_overhang: int = 100,
threads: int = 1,
overwrite: bool = False,
**kwargs: Any,
) -> Path
Build the STAR genome index for the bound assembly and annotation.
Output goes to <LIULAB_DATA>/genome/<assembly>/index/star_<gtf_key>/.
An index whose completion record says it finished is reused unless
overwrite=True; a directory holding index files that no record
vouches for raises rather than being silently rebuilt. The annotation GTF
is resolved from the bound gtf key via
:meth:~genome.annotation.registry.AnnotationRegistry.path and passed to STAR as
--sjdbGTFfile for splice-junction-aware indexing.
Only the most commonly tuned options are named below. Any other STAR
genomeGenerate option may be passed as a keyword argument using its
STAR name without the leading -- (e.g. genomeSAindexNbases=11);
for the meaning of those options see the STAR manual / STAR --help.
Two of them are sized from the assembly rather than left to STAR's
defaults — the suffix-array index size genomeSAindexNbases, from the
total sequence length, and the genome-storage bin size
genomeChrBinNbits, from the mean sequence length and the read length
sjdb_overhang implies. Passing either one yourself wins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sjdb_overhang
|
int
|
|
100
|
threads
|
int
|
|
1
|
overwrite
|
bool
|
Rebuild even if a finished index already exists, and rebuild over a directory that cannot be trusted rather than raising on it. |
False
|
**kwargs
|
Any
|
Extra |
{}
|
Returns:
| Type | Description |
|---|---|
Path
|
The genome directory (also available as :attr: |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the index directory holds files without a record, a record that
disagrees with them, or a record pinning a different assembly digest
than the one registered now. Pass |
RuntimeError
|
If STAR exits non-zero. |
genome.annotation ¶
Annotations — a GTF filed under one Assembly, and everything asked of it after.
I/O boundary package. A reference assembly may carry several gene annotations (GENCODE, RefSeq, WormBase, …). Each is registered under a Registered name and lives in its own directory beside the assembly's sequence files::
<LIULAB_DATA>/genome/<assembly>/gtf/<name>/
<name>.gtf # the annotation, kept decompressed
<name>.db # the gffutils SQLite database built from it
.completion.json # the record saying all of that finished
.work/ # the disposable working area a fetch downloads into
:class:~genome.annotation.registry.AnnotationRegistry is the way in, and it is one
class with one interface. Bound once to one assembly — its name, its Assembly dir and
its chrom.sizes — it holds every annotation that assembly has and answers everything
about them. Its implementation is spread over four modules and it calls across them:
- :mod:
~genome.annotation.registrationputs an annotation on disk: the fetch, the placement, the Chromosome check, the repair-command strings, the Completion marker, the Merged annotation a Chimera build derives, and the two registrars addressed by assembly name. - :mod:
~genome.annotation.registryholds the class itself, the three scans and the Default annotation rule it settles at construction, and the by-assembly-name questionsgenome annotation list,genome annotation gene-listandgenome annotation gene-categoriesask. - :mod:
~genome.annotation.stemsresolves a Gene id stem against an annotation's own gene ids — the seam the Xref, Orthology and TF contexts all cross, and the only one. - :mod:
~genome.annotation.databaseis thegffutilsadapter: the build, and the read that yields gene ids a row at a time. Nothing else in the package imports the library.
Two more modules sit beside those four because they are about the same thing and nothing
else: :mod:~genome.annotation.metadata is the curated table of what the lab supports for
each assembly, keyed by assembly plus Registered name, and
:mod:~genome.annotation.curated reads the shipped Curated gene list — which of an
annotation's genes are in a hand-curated category.
Nothing here knows what a caller's gene ids are a list of. Which species selects a shipped file, and what a row of one says, are facts this package has no stake in, so it holds no import of any of them and gains no method when a fourth topic arrives.
What a registration answers with is re-exported here beside what produces it, so a caller holding one imports it from the package rather than from the module that happens to define it.
Examples:
>>> from pathlib import Path
>>> from genome.annotation import annotation_dir
>>> annotation_dir(Path("/data/genome/sacCer3"), "ensgene_v101").name
'ensgene_v101'
CuratedGeneListError ¶
Bases: ValueError
A shipped curated gene list cannot be read, so it is not allowed to answer.
A packaging defect and not a caller error: these files ship inside the package,
so bad JSON, a missing key, a category with no genes, a gene id in two categories or
an annotation field disagreeing with the file name are all faults in what was
committed here. A :class:ValueError, because a hand-curated file that says something
the format does not is a bad value rather than a broken program.
The message names the file and what is wrong with it, since fixing the file is the only thing anyone can do about it.
Examples:
GeneCategoryNotDeclaredError ¶
GeneCategoryNotDeclaredError(
annotation: str,
assembly: str,
category: str,
declared: Iterable[str],
)
Bases: LookupError
The annotation declares categories, and not the one asked for.
The second of the two absences. It is a real answer about a real curated list — the category was not curated for this annotation, which is not the same as its being curated and empty, and no shipped category is ever empty. The message lists the ones it does declare, since those are what a caller may ask for instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name that was asked about. |
required |
assembly
|
str
|
The Assembly it is registered for. |
required |
category
|
str
|
The category that is not declared. |
required |
declared
|
iterable of str
|
The categories that are, in the order the curated lists spell them. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
annotation |
str
|
The name asked about. |
assembly |
str
|
The assembly it is registered for. |
category |
str
|
The category nobody declared. |
declared |
tuple of str
|
The categories that are declared. |
Examples:
>>> try:
... raise GeneCategoryNotDeclaredError("mine", "tiny", "tRNA", ["rRNA"])
... except LookupError as error:
... print("rRNA" in str(error))
True
GeneListAssemblyMismatchError ¶
Bases: CuratedGeneListError
A curated list was asked for by an annotation registered against another assembly.
The list names the Assembly it was curated against, and that is checked rather than assumed: an annotation registered under a name whose curated list belongs to a different reference would otherwise answer with another species' gene ids, and mixing builds is an error and not a warning.
Not one of the two absences: nothing is missing, so a caller catching
:class:LookupError for absence does not swallow this.
Examples:
NoGeneCategoriesError ¶
NoGeneCategoriesError(
annotation: str,
assembly: str,
curated: Iterable[str],
*,
contributors: Iterable[str] = (),
)
Bases: LookupError
Nothing is known about this annotation's categories, so none can be asked of it.
The first of the two absences, and the one a caller must never read as this annotation has no rRNA genes: no curated list ships for it, so the question was not answered rather than answered with nothing. The message says which annotations do declare categories and that answering for this one means shipping a curated list.
A :class:LookupError, so a caller may catch it together with
:class:GeneCategoryNotDeclaredError and still act differently on each.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name that was asked about — the merged name for a Merged annotation. |
required |
assembly
|
str
|
The Assembly it is registered for. |
required |
curated
|
iterable of str
|
The annotations a curated list does ship for. |
required |
contributors
|
iterable of str
|
For a Merged annotation, the annotations it merges. Shipping a list under the merged name would fix nothing; it is these that need one. |
()
|
Attributes:
| Name | Type | Description |
|---|---|---|
annotation |
str
|
The name asked about. |
assembly |
str
|
The assembly it is registered for. |
curated |
tuple of str
|
The annotations that do ship a curated list. |
contributors |
tuple of str
|
The contributing annotations, empty for anything but a merge. |
Examples:
>>> try:
... raise NoGeneCategoriesError("mine", "tiny", ["gencode_v50"])
... except LookupError as error:
... print("gencode_v50" in str(error))
True
AnnotationMetadata
dataclass
¶
AnnotationMetadata(
assembly: str,
name: str,
provider: str,
version: str,
url: str,
sha256: str | None = None,
default: bool = False,
)
One annotation the lab supports for one assembly (one row of the annotation table).
Keyed by assembly plus name — the Registered name the annotation is
addressed by everywhere — and carrying enough to register it from that name alone:
who publishes it, which release, where to fetch it, and what the unpacked GTF
that source yields hashes to. A complete record is also what the
registration functions accept in place of the table's own row.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly this annotation belongs to — an annotation belongs to exactly one. |
name |
str
|
The Registered name, unique within the assembly, e.g. |
provider |
str
|
Who publishes it: |
version |
str
|
The provider's own release identifier, e.g. |
url |
str
|
Where the GTF is fetched from. |
sha256 |
str or None
|
Digest of the unpacked GTF, or |
default |
bool
|
Whether this is the assembly's Default annotation. |
Examples:
>>> record = AnnotationMetadata(
... assembly="sacCer3",
... name="ensgene_v101",
... provider="UCSC",
... version="ensGene.v101",
... url="https://hgdownload.soe.ucsc.edu/goldenPath/sacCer3/bigZips/genes/sacCer3.ensGene.gtf.gz",
... )
>>> record.provider
'UCSC'
>>> record.default # not the default unless the row says so
False
from_row
classmethod
¶
Build a record from one row of an annotation table.
:meth:AssemblyMetadata.from_row for the annotation table's own columns, and
the same rules — with one more: default is a flag column, where a blank cell
is the real answer no rather than an unknown.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Column name to cell, as :meth: |
required |
Returns:
| Type | Description |
|---|---|
AnnotationMetadata
|
The record the row spells. |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
As :meth: |
Examples:
ChromosomeMismatchError ¶
Bases: ValueError
A GTF names Chromosomes its assembly does not carry, so the two do not line up.
Registering it would build an annotation where nothing matches: every feature would sit on a sequence the assembly has never heard of, and every query over it would answer nothing while looking perfectly healthy. The usual cause is a spelling difference rather than a wrong file, so the message says which one out loud and names the argument that registers it anyway.
The check behind it is strict in one direction only. An assembly carrying scaffolds the annotation never mentions is normal and is not this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name the annotation was being registered under. |
required |
missing
|
iterable of str
|
Every name the GTF uses that the assembly's |
required |
known
|
iterable of str
|
The names the assembly does carry, for the message to contrast against. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The registered name. |
missing |
tuple of str
|
Every offending name, sorted — the message lists at most ten of them and counts the rest, this is the whole set. |
known |
tuple of str
|
The names the assembly carries, as they were passed in. |
Examples:
>>> raise ChromosomeMismatchError("gencode_v44", ["1", "2"], ["chr1", "chr2"])
Traceback (most recent call last):
genome.annotation.registration.ChromosomeMismatchError: the GTF for 'gencode_v44' ...
GtfAnnotation
dataclass
¶
A registered GTF annotation: its name and the on-disk GTF + database paths.
MergeSource
dataclass
¶
One component's contribution to a Merged annotation.
What :func:register_merged_gtf needs about a single component: whose sequences the
features sit on, which of that component's annotations was taken, and where its GTF
is. The component name is not decoration — it is the suffix every seqname the merge
writes carries, so the merged features land on the chimera's own
chromosome names.
Attributes:
| Name | Type | Description |
|---|---|---|
component |
str
|
The Component assembly name, alphanumeric. |
annotation |
str
|
The Registered name of that component's contributing annotation. |
gtf |
Path
|
That annotation's placed GTF, read one line at a time and never in full. |
Examples:
>>> from pathlib import Path
>>> MergeSource("ce11", "wormbase_ws298", Path("/data/ce11.gtf")).component
'ce11'
RegisteredAnnotation
dataclass
¶
What registering one annotation produced: its record, and where that landed.
:func:register_annotation's answer and :func:register_gtf's — what genome
annotation register and genome annotation register-gtf print, and what their --json
serializes. A :class:GtfAnnotation says where an annotation's two files are; this
says what the run that wrote them did, which is the Completion marker itself,
carried whole. Every question a surface then asks — the digest, the source, the files
claimed, whether the chromosome names were actually checked — is answered from that one
record rather than by reading the directory again.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly the annotation belongs to. It is not in the record, which names the annotation rather than what it annotates. |
directory |
Path
|
The annotation's own directory, |
record |
CompletionRecord
|
The record the registration wrote, read back. |
Examples:
>>> from pathlib import Path
>>> from genome.store.completion import CompletionRecord
>>> registered = RegisteredAnnotation(
... assembly="hg38",
... directory=Path("/data/genome/hg38/gtf/gencode_v50"),
... record=CompletionRecord(
... kind="annotation",
... name="gencode_v50",
... files={"gencode_v50.gtf": 12, "gencode_v50.db": 34},
... source_url="https://example.org/gencode_v50.gtf.gz",
... sha256="1a2b3c",
... tool_versions={},
... package_version="2026.8.0",
... completed_at="2026-08-12T09:00:00+00:00",
... details={"chromosomes_checked": True},
... ),
... )
>>> registered.name, registered.file_names
('gencode_v50', ['gencode_v50.db', 'gencode_v50.gtf'])
>>> print(registered.chromosome_check)
chromosomes checked — every name the GTF uses is one the assembly carries
source_url
property
¶
The URL fetched, or the path a GTF was handed over at; None for a merge.
file_names
property
¶
Every file the record claims, sorted — a fresh list each call.
chromosome_check
property
¶
The one line saying what the chromosome-name check settled for this annotation.
:func:chromosome_check_summary over the record this registration wrote, so the
surface that prints it never reads the record's own keys. Always a sentence:
silence would read as a pass.
as_json ¶
Return this registration as --json serializes it.
The record's own fields under the record's own names, then the assembly it
belongs to and the directory it landed in — the two facts a record does not
hold about itself. The names are the ones written on disk and are never respelled
here.
Returns:
| Type | Description |
|---|---|
dict
|
The record's fields, followed by |
AnnotationNotRegisteredError ¶
AnnotationNotRegisteredError(
assembly: str,
name: str,
registered: Iterable[str],
offered: Iterable[str],
*,
broken: BrokenAnnotation | None = None,
)
Bases: KeyError
No annotation of that name is registered here, so there is no path to hand back.
Routinely not a mistake. An assembly's Default annotation comes from the
curated table, and on a fresh machine the table's choice is exactly what nobody has
registered yet — so a :class:~genome.assembly.genome.Genome opens with that default named
and only asking for its path raises, naming the command that closes the gap. The
other way in is a name nothing knows, and the message then says what the table does
offer and how to register a GTF it does not list.
A third way in is a directory that is there and cannot be trusted, which is not
registered either. The next action is then neither of the above — registering it
plainly would itself raise and demand --force — so broken carries what
:func:list_broken_annotations found and the message quotes its repair, which is a
command that runs as it stands.
A :class:KeyError, because that is what asking a registry for a name it does not
hold has always been.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly the annotation was asked for. |
required |
name
|
str
|
The Registered name that is not registered. |
required |
registered
|
iterable of str
|
The names that are registered on this machine. |
required |
offered
|
iterable of str
|
The names the annotation table offers for this assembly. |
required |
broken
|
BrokenAnnotation
|
The broken registration filed under |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly asked about. |
name |
str
|
The name that is not registered. |
registered |
tuple of str
|
The registered names, as they were passed in. |
offered |
tuple of str
|
The offered names, as they were passed in. |
broken |
BrokenAnnotation or None
|
The broken registration, or |
Examples:
>>> raise AnnotationNotRegisteredError("hg38", "gencode_v50", [], ["gencode_v50"])
Traceback (most recent call last):
genome.annotation.registry.AnnotationNotRegisteredError: "no annotation ...
AnnotationRegistry ¶
AnnotationRegistry(
assembly_dir: AssemblyDir,
*,
chrom_sizes: str | Path | None = None,
default: str | None = None,
)
One Assembly's annotations, the state each is in, and the acts that add one.
An annotation directory is registered, broken, offered but not begun, or nothing at all, and every useful question about one is a question about that four-way state: what may a caller name, what may it be handed the path of, which is the Default annotation, what does a surface print, what does a name nobody registered earn as an error. This settles all four once, at construction, and answers from that — so the state is assembled in one place rather than wherever it is needed.
Bound to one assembly and carried, never re-derived: the Assembly dir comes in as
an :class:~genome.assembly.registration.AssemblyDir, so a registry cannot file an
annotation somewhere other than where the caller that built it is looking, and the
chrom.sizes every GTF is checked against comes in beside it rather than being
guessed from the layout.
Reading is cheap and safe: nothing here is created, fetched or built by asking, an
assembly with no directory at all answers emptily, and one broken annotation is
reported rather than raised over. Only :meth:register and :meth:register_path
write, and both fold what they wrote back in, so the four states stay current without
reading the disk again.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_dir
|
AssemblyDir
|
The assembly this registry is for, and where its |
required |
chrom_sizes
|
str or Path
|
The assembly's |
None
|
default
|
str
|
A Default annotation the caller chose, which wins over the table's flag and
need not be registered. See :func: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly every annotation here belongs to. |
Examples:
>>> registry = AnnotationRegistry.locate("sacCer3", "/tmp/definitely-not-an-assembly")
>>> registry.registered
[]
>>> registry.default # the table's flag, registered or not
'ensgene_v101'
registered
property
¶
The Registered names on this machine, in directory-name order.
What is here, as against :attr:offered, which is what the lab supports, and
:attr:broken, which is what is here and cannot be trusted.
broken
property
¶
The annotation directories here that cannot be trusted as finished.
What :attr:registered leaves out, and between the two every directory under
gtf/ is accounted for. Each entry says what is wrong and names the one command
that repairs it.
offered
property
¶
The annotation table's rows for this assembly, in table order.
What the lab supports, whether or not anyone has registered it. Empty for an assembly the table offers nothing for, which is legal: it is a cross-reference rather than an allow-list.
default
property
¶
Name of the Default annotation, or None when nothing decides one.
:func:default_annotation's answer for this assembly, settled when the registry
was built. It may name an annotation nobody has registered here — the normal state
of a fresh machine — so it is :meth:path that says whether one exists. A default
already decided is never displaced by a later registration.
locate
classmethod
¶
locate(
assembly: str,
cache_dir: str | Path | None = None,
*,
default: str | None = None,
) -> AnnotationRegistry
Return the registry for assembly, wherever the layout says its files live.
The assembly-addressed way in, and the only one the CLI has: a name and at most a
directory override. :meth:~genome.assembly.registration.AssemblyDir.locate is where
that override rule lives, and the chrom.sizes is the one that layout names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to open the registry of, e.g. |
required |
cache_dir
|
str or Path
|
An explicit Assembly dir, overriding the Data dir layout. |
None
|
default
|
str
|
A Default annotation the caller chose. |
None
|
Returns:
| Type | Description |
|---|---|
AnnotationRegistry
|
Its registry. Nothing is created and nothing is fetched. |
Examples:
path ¶
Return the GTF file path of the annotation registered as name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name to resolve. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
Path to the placed |
Raises:
| Type | Description |
|---|---|
AnnotationNotRegisteredError
|
If nothing of that name is registered here. The four-way state decides what
the message says next: the command that registers |
Examples:
register ¶
register(
name: str,
*,
force: bool = False,
progressbar: bool = True,
metadata: AnnotationMetadata | None = None,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> GtfAnnotation
Register the annotation the table lists for this assembly as name.
Naming an annotation is enough: where its GTF comes from and which digest it must match are the curated table's to know. The row's URL is fetched into the working area, the unpacked GTF is checked against the sha256 the row pins — so a GTF that is not the pinned one never reaches the annotation directory — the gffutils database is built, and the record is written last.
Its chromosome names are checked too, against this registry's chrom.sizes and
while the GTF is still in the working area: every name the GTF uses must be one the
assembly carries, so an Ensembl-spelled GTF registered against a UCSC-spelled
assembly fails in seconds rather than after the minutes the database build takes.
The reverse is not required — an assembly may carry scaffolds the annotation never
mentions. An assembly with no chrom.sizes yet has nothing to check against, and
the record says so in details["chromosomes_checked"] — with
details["chromosomes_unchecked_because"] saying whether that was for want of a
chrom.sizes or because the caller stood the check down.
An annotation that already has a valid record is returned silently: nothing is
fetched, nothing is rebuilt and nothing is warned about. A directory that cannot be
trusted — files with no record, or a record that disagrees with disk — raises,
naming genome annotation register <assembly> <name> --force. That is
what force=True is: it skips the question, keeps a GTF whose digest can be shown
to be the pinned one, and fetches the source again when it cannot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name the table lists, e.g. |
required |
force
|
bool
|
Register again from scratch, repairing a directory that raises. |
False
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
metadata
|
AnnotationMetadata
|
A complete annotation record to use instead of the curated table's row. Omit it and the row is looked up here. |
None
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
GtfAnnotation
|
The registered annotation's name and its two file paths. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the table lists no annotation |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry; the message lists them and names the usual cause. |
ChecksumMismatchError
|
If the row pins a sha256 and the unpacked GTF is not it; the message names both digests. |
UnfinishedRegistrationError
|
If the annotation's directory holds files but no record. |
RegistrationMismatchError
|
If its record disagrees with what is on disk. |
Examples:
register_path ¶
register_path(
gtf: str | Path,
name: str,
*,
force: bool = False,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> GtfAnnotation
Register the GTF at gtf under name and build its gffutils database.
The escape hatch for an annotation the curated table does not list —
:meth:register is the way in for one it does. A gzipped (.gz) source is
decompressed into the registered <name>.gtf; a plain GTF is copied as-is. The
digest recorded is of the placed GTF, since an unlisted annotation has no pinned
digest to compare against.
Its chromosome names are checked against this registry's chrom.sizes before
anything is created, so a GTF that does not line up leaves the annotation directory
exactly as it was found. Knowing the assembly is what buys that: the file is found
rather than passed, so an unlisted GTF is held to the same check a listed one gets.
Registering something already registered returns it silently, and a directory that
cannot be trusted raises naming genome annotation register-gtf <assembly> <gtf> <name>
--force, exactly as :meth:register does for a listed one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gtf
|
str or Path
|
Path to the source GTF, plain or |
required |
name
|
str
|
The Registered name to address it by, unique within the assembly. |
required |
force
|
bool
|
Register again from scratch — the repair for a directory that raises. |
False
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
GtfAnnotation
|
The registered annotation's name and its two file paths. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry. |
RegistrationError
|
If the annotation's directory cannot be trusted as finished. |
Examples:
status ¶
Report what this assembly's table offers against what is registered here.
Two questions with two answers, joined for one reader: the table's rows say what
the lab supports, the disk says what is on this machine, and every row carries
which of the two it is. The command behind it is genome annotation list.
A third answer rides along, because this is where a reader would look for it: a
directory that cannot be trusted is broken rather than registered. Nothing
raises — reporting a broken annotation is the point, and one of them must not cost
the rest.
Returns:
| Type | Description |
|---|---|
AnnotationStatus
|
The assembly, its directory, the Default annotation's name, and one
:class: |
Examples:
gene_list ¶
Return the genes one registered annotation puts in category.
The genes come from the Curated gene list shipped for that annotation and never from the GTF's own biotype attribute, which is spelled two ways across four publishers, carries three taxonomies that do not agree, and is absent altogether from some annotations. Nothing here knows a category vocabulary: which categories exist is what the curated list declares.
The annotation must be registered here — it is resolved through :meth:path,
so an unregistered name earns the error that names the command registering it.
The curated list is then held to the assembly it was curated against, since a name
is unique only within its assembly and a list found by name alone is not yet known
to be about this reference.
For a Merged annotation the record's merged_from says who contributed, and
each contributor's own curated list answers for its own Component: the result
carries one source per contributor that declares the category, so a caller counting
worm ribosomal RNA can drop the E. coli entry. A contributor that does not
declare it is simply absent — a bacterium has no mitochondria, and that is not a
failure.
There is no empty answer. An annotation nothing ships a list for, and one whose list does not declare this category, are different facts and each raises an error of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
category
|
str
|
The Gene category to ask for, as the curated list spells it — |
required |
name
|
str
|
The Registered name to ask about. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
GeneList
|
The category, its gene ids, and one
:class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneCategoriesError
|
If no curated list ships for that annotation — nothing can be asked of it, which is not the same answer as its having no genes in this category. |
GeneCategoryNotDeclaredError
|
If it declares categories and not this one; the message lists the ones it does. |
GeneListAssemblyMismatchError
|
If the curated list found under that name was curated against another assembly, in which case it must not answer here. |
Examples:
gene_lists ¶
Return every Gene category one registered annotation declares.
:meth:gene_list for all of them at once, in the order the curated lists spell
them — and for a Merged annotation, each contributor's own order, contributors
first-listed first. Everything :meth:gene_list says about resolution, the
assembly guard and attribution holds here.
Never an empty tuple. An annotation that declares nothing raises rather than
answering emptily, which is the whole distinction this surface exists to keep: a
caller that got () could not tell no categories are declared from every
category is empty, and no declared category is ever empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Registered name to ask about. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of GeneList
|
One entry per declared category, in declaration order. Never empty. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneCategoriesError
|
If no curated list ships for that annotation. |
GeneListAssemblyMismatchError
|
If a curated list found by name was curated against another assembly. |
Examples:
resolve_gene_ids ¶
Return the gene ids one registered annotation carries for each Gene id stem.
A stem is a gene id with its version dropped — ENSG00000123456 for
ENSG00000123456.7 — which is how every published table keyed by gene arrives,
and never how a GENCODE Annotation spells the same gene. This is the crossing:
every gene id in the Annotation database is reduced to its own stem, and a stem
answers with every gene id that reduced to it. An id carrying no version is its own
stem, so an annotation whose ids were never versioned — WormBase's, SGD's — resolves
each of its genes to itself and is untouched by an Ensembl-shaped assumption.
Every id, and never a chosen one. One stem naming two gene ids is not a
malformed annotation: gencode_v50lift37 has nine such stems, eight of them
pseudoautosomal genes carrying a _PAR_Y copy, and a resolver taking the first
would hand back the X copy of a Y gene without saying it had chosen. So the answer
is a mapping to all of them, ascending.
Nothing is dropped. Stems this annotation carries no gene for come back in
:attr:~genome.annotation.stems.ResolvedGeneIds.unresolved, so a caller resolving
a few thousand at once
can see which of them this annotation does not have rather than counting the
answer and wondering.
The annotation must be registered here, and a Merged annotation is read
exactly as any other: it has one database of its own, holding both components' gene
features under the components' own gene ids — a merge rewrites seqnames and never a
gene_id — so a stem naming a gene in each component answers with both, which is
the same rule as the pseudoautosomal one and needs no attribution to apply it.
One indexed pass over the database's gene features answers the whole call, however many stems it was handed; nothing reads the GTF, and no annotation is held in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stems
|
iterable of str
|
The Gene id stems to resolve, in the order they should come back. Repeats are asked once. Pass them all at once: the cost is the pass, not the stem. |
required |
name
|
str
|
The Registered name to resolve against. Omitted, this assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
ResolvedGeneIds
|
The stems that named gene ids, mapped to every id each names, and the stems that named none. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered here. |
NoGeneFeaturesError
|
If its database holds no gene at all — every stem would otherwise resolve to nothing, which is a different fact and must not be reported as this one. |
Examples:
AnnotationStatus
dataclass
¶
AnnotationStatus(
assembly: str,
directory: Path,
default_annotation: str | None,
annotations: tuple[AnnotationStatusRow, ...],
)
What one assembly's table offers, set against what is registered on this machine.
:meth:AnnotationRegistry.status's answer, and what genome annotation list prints. Two
questions joined for one reader, with a third riding along because this is where anyone
would look for it: a directory that cannot be trusted is broken rather than
registered, and reporting one is the point — nothing here raises.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly reported on. |
directory |
Path
|
Its Assembly dir, whether or not anything is there. |
default_annotation |
str or None
|
The Default annotation's name, or |
annotations |
tuple of AnnotationStatusRow
|
One row per name: the offered ones in table order, then anything on this disk that no row lists. |
Examples:
>>> from pathlib import Path
>>> status = AnnotationStatus(
... assembly="hg38",
... directory=Path("/data/genome/hg38"),
... default_annotation=None,
... annotations=(),
... )
>>> status.default_row is None
True
>>> status.default_summary
'default: (none)'
>>> status.as_json()["directory"]
'/data/genome/hg38'
default_row
property
¶
The Default annotation's own row, or None when no row is about it.
None covers both of the ways that happens, and a caller wanting to tell them
apart reads :attr:default_annotation beside this: nothing decided a default, or
one is decided and the table lists it under a name this disk knows nothing about.
default_summary
property
¶
The closing sentence naming the Default annotation, and how to get it.
Four answers, and three of them tell the reader what to do next: nothing decided a
default; one is decided and registered, which needs no advice; one is decided and
broken, which is repaired by the command its own row already carries; or one is
decided and absent, which is the ordinary state of a fresh machine and is
registered by :func:annotation_register_command. Both commands come off an
interface rather than being assembled here, so the two halves of this sentence
cannot drift apart.
Returns:
| Type | Description |
|---|---|
str
|
One line, beginning |
as_json ¶
Return this report as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
AnnotationStatusRow
dataclass
¶
AnnotationStatusRow(
name: str,
offered: bool,
registered: bool,
broken: bool,
default: bool,
provider: str | None,
version: str | None,
url: str | None,
sha256: str | None,
path: str | None,
problem: str | None,
repair: str | None,
)
One annotation, in whichever of its states it is: offered, registered, broken.
One shape for all of them, so a reader never has to ask which fields a row has — a
name the table does not list carries the table's columns as None, and one nothing
is wrong with carries the broken columns as None. :attr:registered and
:attr:broken are never both true: a registration nothing vouches for is not one, and
:attr:state is that invariant said in one word.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The Registered name this row is about. |
offered |
bool
|
Whether the annotation table lists it for this assembly. |
registered |
bool
|
Whether a record here vouches for it. |
broken |
bool
|
Whether its directory is here and cannot be trusted. |
default |
bool
|
The table's own default flag, |
provider |
str or None
|
Who publishes it, from the table's row; |
version |
str or None
|
The provider's release identifier; |
url |
str or None
|
Where the table says its GTF is fetched from; |
sha256 |
str or None
|
The digest the table pins; |
path |
str or None
|
The registered GTF's path, or |
problem |
str or None
|
What is wrong, when :attr: |
repair |
str or None
|
The command that registers it again from scratch, when :attr: |
Examples:
>>> row = AnnotationStatusRow(
... name="gencode_v50",
... offered=True,
... registered=False,
... broken=False,
... default=True,
... provider="GENCODE",
... version="v50",
... url="https://example.org/gencode_v50.gtf.gz",
... sha256=None,
... path=None,
... problem=None,
... repair=None,
... )
>>> row.as_json()["offered"]
True
>>> row.state
'offered, not registered'
state
property
¶
Which of its states this row is in, in the words a surface prints.
broken first, because the three fields it is read from are not independent:
a broken annotation is not registered — no record vouches for it — so answering
with the absence of one would be true and useless, and it is the state that needs
acting on. A row nothing offers is one this disk holds and the table does not, so
the only thing left to say about it is that.
Returns:
| Type | Description |
|---|---|
str
|
One of |
as_json ¶
Return this row as --json serializes it: every attribute above, in order.
:attr:state is not among them: it is read from :attr:broken, :attr:offered
and :attr:registered, which are all here, so writing it out too would be a
second spelling of the same rule for a reader to disagree with.
Returns:
| Type | Description |
|---|---|
dict
|
The row's fields, under their own names. |
BrokenAnnotation
dataclass
¶
An annotation directory that is there and cannot be trusted as finished.
What :func:list_annotations leaves out, said out loud. It is not a
:class:GtfAnnotation and carries no file paths, because the whole point is that
nothing vouches for the files: what it carries instead is why it cannot be trusted
and the one command that makes it trustworthy again.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The Registered name its directory is filed under. |
directory |
Path
|
The annotation directory, whatever state it is in. |
problem |
str
|
What is wrong, in full — which files disagree or which are there with no record
— ending in the |
repair |
str
|
The command that registers it again from scratch. |
Examples:
>>> from pathlib import Path
>>> broken = BrokenAnnotation(
... name="mine",
... directory=Path("/data/genome/hg38/gtf/mine"),
... problem="... holds files but no .completion.json ...",
... repair="genome annotation register-gtf hg38 /tmp/mine.gtf mine --force",
... )
>>> broken.repair
'genome annotation register-gtf hg38 /tmp/mine.gtf mine --force'
GeneList
dataclass
¶
The genes one annotation puts in one Gene category, attributed to their sources.
:meth:AnnotationRegistry.gene_list's answer — what genome annotation gene-list
prints and what its --json serializes. There is no empty one: an annotation that declares no
categories, and one that declares categories but not this one, each raise a
:class:LookupError of their own rather than answering with nothing, so holding one of
these means the category was really declared and really has genes in it.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly asked about. |
annotation |
str
|
The Registered name asked about — the merged name for a Merged annotation,
whose contributors are named in :attr: |
category |
str
|
The Gene category, as the curated lists spell it. |
sources |
tuple of GeneListSource
|
One entry per contributing Curated gene list, in contributor order. Never empty. A contributor that does not declare this category is simply absent — a bacterium has no mitochondria, and that is not a failure to report. |
Examples:
>>> genes = GeneList(
... assembly="ce11",
... annotation="wormbase_ws298",
... category="rRNA",
... sources=(
... GeneListSource(None, "wormbase_ws298", "rRNA genes", "WormBase", ("a", "b")),
... ),
... )
>>> genes.gene_ids
['a', 'b']
>>> genes.as_json()["category"]
'rRNA'
gene_ids
property
¶
Every source's gene ids, concatenated in source order — a fresh list each call.
Concatenated and not de-duplicated. A merge rewrites only the seqname and
never the gene_id, so two components carrying the same id would be a real
ambiguity in the merged annotation, and collapsing it here would hide exactly that
— a caller summing over these would silently under-count one of the two. Where
that matters, :attr:sources says which contributor each id came from.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
GeneListSource
dataclass
¶
GeneListSource(
component: str | None,
annotation: str,
description: str,
source: str,
gene_ids: tuple[str, ...],
)
One Curated gene list that contributed to an answer, and what it contributed.
What makes a Merged annotation's genes attributable: one of these per contributing
annotation, so a caller counting worm ribosomal RNA can drop the E. coli entry
rather than being handed one number it cannot take apart. An annotation that is not a
merge has exactly one, whose component is None.
description and source travel with the ids rather than being looked up
separately, because they are what says whether these ids mean what the caller's metric
needs: two annotations spelling a category the same way need not have curated it the
same way.
Attributes:
| Name | Type | Description |
|---|---|---|
component |
str or None
|
The Component assembly whose genes these are, for a contributor to a Merged
annotation; |
annotation |
str
|
The Registered name of the contributing annotation. |
description |
str
|
What membership in this category means for that annotation. |
source |
str
|
Where that membership came from, and the caveats on using it. |
gene_ids |
tuple of str
|
The gene ids it contributed, in the order its curated list lists them. |
Examples:
>>> source = GeneListSource(
... component="ce11",
... annotation="wormbase_ws298",
... description="the mature ribosomal RNA genes",
... source="WormBase WS298 gene_biotype",
... gene_ids=("WBGene00004512", "WBGene00004513"),
... )
>>> source.as_json()["component"]
'ce11'
>>> len(source.gene_ids)
2
as_json ¶
Return this contribution as --json serializes it: every attribute, in order.
Returns:
| Type | Description |
|---|---|
dict
|
The fields above, under their own names, with |
NoGeneFeaturesError ¶
Bases: LookupError
An annotation's database holds no gene at all, so no gene id can be resolved.
The absence a caller must never read as this annotation carries none of my genes.
A GTF that declares only exons registers as exons alone —
:meth:~genome.annotation.registry.AnnotationRegistry.register leaves Feature
inference off, and rightly, since it is the library's slow path and the publishers
who matter declare their genes — so an
annotation like that would answer every stem with not found while looking perfectly
healthy. It says so instead, and names the argument that rebuilds it with the genes in.
A :class:LookupError, as the other absences on this surface are.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
annotation
|
str
|
The Registered name that was asked about. |
required |
assembly
|
str
|
The Assembly it is registered for. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
annotation |
str
|
The name asked about. |
assembly |
str
|
The assembly it is registered for. |
Examples:
>>> try:
... raise NoGeneFeaturesError("mine", "tiny")
... except LookupError as error:
... print("--infer-genes" in str(error))
True
ResolvedGeneIds
dataclass
¶
ResolvedGeneIds(
assembly: str,
annotation: str,
resolved: Mapping[str, tuple[str, ...]],
unresolved: tuple[str, ...],
)
The gene ids one Annotation carries for the Gene id stems it was asked about.
:meth:~genome.annotation.registry.AnnotationRegistry.resolve_gene_ids's answer,
and the one result type two
contexts share: it is defined beside the call that returns it, and
:mod:genome.tf.gene, :mod:genome.tf.cofactor and :mod:genome.homology.annotation
import it from here. A stem is a gene id with its version dropped, and
inside one annotation it may name more than one gene id — nine do in
gencode_v50lift37, eight of them pseudoautosomal-Y — so every stem answers with
all of them and nothing here picks one. Two stems never name the same gene id,
since an id has exactly one stem.
What was asked about and is not there rides back on the answer. A caller holding a
few thousand stems gets the ones this annotation carries no gene for in
:attr:unresolved rather than a shorter list than it passed, so what the thing it was
holding contains and this annotation does not is visible instead of dropped.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly asked about. |
annotation |
str
|
The Registered name whose own gene ids these are. |
resolved |
mapping of str to tuple of str
|
Every stem that named at least one gene id, in the order the stems were asked
about, to the ids it names, in ascending order. No value is ever an empty tuple —
a stem that named nothing is in :attr: |
unresolved |
tuple of str
|
The stems no gene id in the annotation is of, in the order they were asked about. |
Examples:
>>> answer = ResolvedGeneIds(
... assembly="hg19",
... annotation="gencode_v50lift37",
... resolved={
... "ENSG00000182378": ("ENSG00000182378.14", "ENSG00000182378.14_PAR_Y"),
... "ENSG00000141510": ("ENSG00000141510.18",),
... },
... unresolved=("ENSG00000288541",),
... )
>>> answer.gene_ids
['ENSG00000182378.14', 'ENSG00000182378.14_PAR_Y', 'ENSG00000141510.18']
>>> answer.as_json()["unresolved"]
['ENSG00000288541']
gene_ids
property
¶
Every gene id resolved, stem order and then id order — a fresh list each call.
Every id, not one per stem. Flattening is exactly where a reader would take
the first id of each stem and lose the second, which is the pseudoautosomal gene
this answer's shape exists to keep; :attr:resolved is what says which stem an id
came from.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
annotation_dir ¶
Return the directory holding the annotation registered as name.
annotation_register_command ¶
Return the command that registers the annotation name for assembly.
The one spelling of it. Errors quote it, the repair adds --force to it, and
:attr:~genome.annotation.registry.AnnotationStatus.default_summary names it for a Default annotation nobody
has fetched yet — so a renamed command is renamed once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The Assembly the annotation belongs to, e.g. |
required |
name
|
str
|
The Registered name to address it by. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A shell command, unquoted and unfenced — the caller decides how to set it. |
Examples:
chromosome_check_summary ¶
Return the one line a surface prints about an annotation's chromosome-name check.
Four states, four sentences, and one of them is always returned: the check ran and passed; it had nothing to run against, and registering the assembly is what fixes that; the caller stood it down, which is not something to advise about; or the record does not say which, and none of the three may be claimed. Silence is not a fifth state — a surface that prints nothing about the check reads as one that passed.
details is a registration record's details; a caller holding what a
registration answered with asks :attr:RegisteredAnnotation.chromosome_check instead
and never spells the two fields. Those are chromosomes_checked — the check ran and
the GTF's names were all the assembly's — and chromosomes_unchecked_because, which
says which of the two reasons it did not, and is None when it did.
A record written before the second field existed carries a bare
chromosomes_checked: false that was written for either reason, and nothing on disk
says which. It reads as unknown rather than as either one, and rather than raising:
the reason is a fact that was never gathered, which is what an absent entry in
tool_versions means too.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
details
|
mapping of str to object
|
A registration record's |
required |
Returns:
| Type | Description |
|---|---|
str
|
One sentence, with no trailing punctuation and no leading indent — the caller decides how to set it. |
Examples:
>>> chromosome_check_summary({"chromosomes_checked": True})
'chromosomes checked — every name the GTF uses is one the assembly carries'
>>> print(chromosome_check_summary({"chromosomes_unchecked_because": "caller-override"}))
chromosomes not checked — the check was stood down, so the record does not vouch for the names
discard_merged_annotation ¶
Remove the Merged annotation registered as name, when that is what it is.
The other half of a chimera build owning its annotation. The merged name is the
+-join of the contributing annotations' names, so a rebuild whose contributing set
changed registers the merge under a new name — and the previous one, which nothing
else will ever write again, would otherwise stay registered beside it. Two derived
annotations with nothing to choose between them is a chimera whose Default
annotation is suddenly none, which is how an annotated chimera comes back from a
legitimate repair with none at all. So the build removes what it no longer owns, and
:meth:~genome.assembly.chimera_build.ChimeraBuilder.build_genome is the only caller.
Owning it is proved, not assumed: only a directory whose record carries the
merged_from marker a merge writes is removed, so an annotation a caller registered
by hand — and a directory nothing vouches for — is left exactly where it is, whatever
it is called.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_dir
|
Path
|
The chimera's Assembly dir, which the annotation is filed under. |
required |
name
|
str
|
The Registered name to remove, as the previous build's completion record names it. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether an annotation was removed. |
Examples:
register_annotation ¶
register_annotation(
assembly: str,
name: str,
*,
force: bool = False,
cache_dir: str | Path | None = None,
progressbar: bool = True,
metadata: AnnotationMetadata | None = None,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> RegisteredAnnotation
Register name for assembly and return the record of what that did.
:meth:~genome.annotation.registry.AnnotationRegistry.register addressed by assembly
name, and answering with the
record rather than the paths — the call genome annotation register makes, and the
one a script makes when it wants to serialize what happened. An annotation that is
already registered is returned from its record without fetching anything.
:func:register_gtf is the same shape for a GTF the table does not
list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly the annotation belongs to, e.g. |
required |
name
|
str
|
The Registered name the table lists, e.g. |
required |
force
|
bool
|
Register again from scratch, repairing a directory that raises. |
False
|
cache_dir
|
str or Path
|
Override which assembly directory the annotation is filed under. Defaults to
:func: |
None
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
metadata
|
AnnotationMetadata
|
A complete annotation record to use instead of the curated table's row. |
None
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
RegisteredAnnotation
|
The completion record the run wrote — |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the table lists no annotation |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry. |
RegistrationError
|
If the directory holds a build that cannot be trusted as finished, or (with
|
ChecksumMismatchError
|
If the row pins a sha256 and the unpacked GTF is not it. |
Examples:
register_gtf ¶
register_gtf(
assembly: str,
gtf: str | Path,
name: str,
*,
force: bool = False,
cache_dir: str | Path | None = None,
check_chromosomes: bool = True,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> RegisteredAnnotation
Register the GTF at gtf for assembly and return the record of what that did.
:meth:~genome.annotation.registry.AnnotationRegistry.register_path addressed by
assembly name, and answering
with the record rather than the paths — the call genome annotation register-gtf makes, and
the way a script registers an annotation the curated table does not list and then
serializes what happened. :func:register_annotation is the same shape for one the
table does list.
Naming the assembly is what lets its chrom.sizes be found rather than passed, so
an unlisted GTF has its chromosome names checked by default, exactly as a listed one
does — and it is what says which reference these gene models are for. An assembly that
is not prepared yet has no chrom.sizes to check against, and the record then says
the names went unchecked — and that it was for want of that file, not because anyone
stood the check down — rather than claiming they passed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly the annotation belongs to, e.g. |
required |
gtf
|
str or Path
|
Path to the source GTF, plain or |
required |
name
|
str
|
The Registered name to address it by, unique within the assembly. |
required |
force
|
bool
|
Register again from scratch, repairing a directory that raises. |
False
|
cache_dir
|
str or Path
|
Override which assembly directory the annotation is filed under. Defaults to
:func: |
None
|
check_chromosomes
|
bool
|
Check the GTF's chromosome names against the assembly's. Pass |
True
|
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
RegisteredAnnotation
|
The completion record the run wrote, with the |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ChromosomeMismatchError
|
If the GTF names sequences the assembly does not carry. |
RegistrationError
|
If the directory holds a build that cannot be trusted as finished, or (with
|
Examples:
register_merged_gtf ¶
register_merged_gtf(
assembly_dir: Path,
name: str,
sources: Sequence[MergeSource],
*,
separator: str,
chrom_sizes: str | Path,
disable_infer_genes: bool = True,
disable_infer_transcripts: bool = True,
) -> GtfAnnotation
Write the Merged annotation of sources under name and build its database.
The annotation half of a Chimera build, called from
:meth:~genome.assembly.chimera_build.ChimeraBuilder.build_genome and from nowhere else. Each
source's GTF is streamed a line at a time into one file whose seqnames carry the
component suffix the chimera's FASTA already carries, which is then placed,
checked, built and recorded exactly as any other annotation is.
No coordinate is converted. Only the first column of each data line is rewritten; every byte after the first tab — including both position fields, which are 1-based and inclusive as GTF has them — is copied through untouched. The features are the components' own features on the components' own sequences, under a new spelling of the sequence name and nothing else.
Comment lines are dropped, all of them. A #!genome-build pragma names the
single assembly its file was built for, and several of those concatenated would each
be false about the chimera; the ordinary # comment beside them describes a file
that no longer exists as such. Nothing else is dropped: a line carrying a tab is a data
line and survives, unsorted and in component order.
The chromosome-name check is not optional here and has no argument that stands it
down. Everything else in the build derives the chimera's names twice — once for the
FASTA and once for this — and the check is the one place those two answers are set
against each other, so a merge that misspelled a name raises
:class:ChromosomeMismatchError rather than registering an annotation that queries
empty.
Nothing on disk is adopted: unlike the other ways in, this one never asks whether the annotation is already registered. It is written by the build that owns it, every time that build runs, which is what makes a stale database impossible to hand back — the name is derived from the contributing annotations, so it changes when they do, but it cannot say which components contributed and would otherwise be reusable under a meaning it no longer has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_dir
|
Path
|
The chimera's Assembly dir, which this annotation is filed under. |
required |
name
|
str
|
The Registered name to write it as — derived by the caller from the contributing annotations' names. |
required |
sources
|
sequence of MergeSource
|
One entry per contributing component, in the order their sequences are written. Must not be empty: no contributors means no annotation, which the caller decides rather than registering an empty one. |
required |
separator
|
str
|
The run of underscores this chimera's chromosome names carry, as
:func: |
required |
chrom_sizes
|
str or Path
|
The chimera's |
required |
disable_infer_genes
|
bool
|
Do not reconstruct |
True
|
disable_infer_transcripts
|
bool
|
Do not reconstruct |
True
|
Returns:
| Type | Description |
|---|---|
GtfAnnotation
|
The registered annotation's name and its two file paths. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ChromosomeMismatchError
|
If a merged seqname is not one the chimera carries — the merge and the FASTA build disagreeing, which nothing else would catch. |
ChimeraNamingError
|
If a component name or the separator does not obey the naming contract. |
Examples:
>>> from pathlib import Path
>>> register_merged_gtf(
... Path("/data/genome/ce11_ecHT115"),
... "wormbase_ws298+refseq_rs_2025_06_26",
... [MergeSource("ce11", "wormbase_ws298", Path("/data/ce11.gtf"))],
... separator="__",
... chrom_sizes=Path("/data/genome/ce11_ecHT115/ce11_ecHT115.chrom.sizes"),
... )
GtfAnnotation(name='wormbase_ws298+refseq_rs_2025_06_26', ...)
annotation_status ¶
Report what assembly's table offers against what is registered on this machine.
:meth:AnnotationRegistry.status for an assembly named rather than opened, which is
what genome annotation list runs. Nothing is prepared, fetched, built or created to
answer it — an assembly with no directory at all is the case it most needs to serve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to report on, e.g. |
required |
cache_dir
|
str or Path
|
Override which assembly directory is inspected. Defaults to
:func: |
None
|
Returns:
| Type | Description |
|---|---|
AnnotationStatus
|
The report :meth: |
Examples:
gene_list ¶
gene_list(
assembly: str,
category: str,
*,
annotation: str | None = None,
cache_dir: str | Path | None = None,
) -> GeneList
Return the genes assembly's annotation puts in category.
:meth:AnnotationRegistry.gene_list for an assembly named rather than opened, which
is what genome annotation gene-list runs. A registry is built for the length of the call, so
there is no second code path. Nothing is prepared, fetched or built to answer it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to ask about, e.g. |
required |
category
|
str
|
The Gene category, as the curated list spells it. |
required |
annotation
|
str
|
The Registered name to ask about; the Default annotation when omitted. |
None
|
cache_dir
|
str or Path
|
Override which assembly directory is inspected, as
:func: |
None
|
Returns:
| Type | Description |
|---|---|
GeneList
|
The answer :meth: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneCategoriesError
|
If no curated gene list ships for it. |
GeneCategoryNotDeclaredError
|
If it declares categories and not this one. |
Examples:
gene_lists ¶
gene_lists(
assembly: str,
*,
annotation: str | None = None,
cache_dir: str | Path | None = None,
) -> tuple[GeneList, ...]
Return every Gene category assembly's annotation declares.
:meth:AnnotationRegistry.gene_lists addressed by assembly name — what genome
annotation gene-categories runs, built the same way :func:gene_list is. Never an
empty tuple: an annotation that declares nothing raises instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to ask about, e.g. |
required |
annotation
|
str
|
The Registered name to ask about; the Default annotation when omitted. |
None
|
cache_dir
|
str or Path
|
Override which assembly directory is inspected. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of GeneList
|
One entry per declared category, in declaration order. Never empty. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If that annotation is not registered here. |
NoGeneCategoriesError
|
If no curated gene list ships for it. |
Examples:
genome.assembly ¶
One Assembly on disk — which reference it is, where its files live, how a locus becomes bases.
The whole Assembly context, and its own I/O with it: nothing outside this package fetches a FASTA, derives a companion file or decides where an assembly's directory is.
- :mod:
~genome.assembly.metadatais the curated table — what the lab supports and how each reference is named across databases. - :mod:
~genome.assembly.sourceresolves a name into where its bytes come from, and :mod:~genome.assembly.downloadgets them; :mod:~genome.assembly.registrationis the half of registering that has nothing to do with where they came from — the Assembly dir layout, the staging, the Completion marker. - :mod:
~genome.assembly.statusreads that layout back: which assemblies the curated table offers, and which of them — plus which names it does not list — are prepared here. - :mod:
~genome.assembly.fastaderives the companions with External tools and :mod:~genome.assembly.twobitreads bases back out of the.2bit. - :mod:
~genome.assembly.chimerais the naming rules a Chimera obeys and :mod:~genome.assembly.chimera_buildwrites one; :mod:~genome.assembly.componentsis what such a build recorded, read back. - :mod:
~genome.assembly.genomeis all of it opened: :class:~genome.assembly.genome.Genome.
What this file re-exports stops short of two modules, and the omission is load-bearing.
:class:~genome.assembly.genome.Genome and the chimera build both reach into
:mod:genome.annotation, which reaches back here for the Assembly dir — so importing
either of them from this file would make import genome.annotation run the whole
open-a-genome stack through a package it is halfway through importing. Genome is
exported from :mod:genome itself, which is where a caller holds it anyway, and so is
:class:~genome.assembly.chimera_build.AmbiguousDefaultAnnotationError — the one error
the chimera build raises. It is public at the root because of this import edge, not
because it belongs to this context any less than
:class:~genome.assembly.chimera.ChimeraNamingError beside it.
Examples:
ChimeraNamingError ¶
Bases: ValueError
A name does not obey the chimera naming contract.
One type covers every rejection this module makes — a component name that is not alphanumeric, a component set that is too small or repeats itself, an assembly name that is not spelled like a chimera's, a chromosome name carrying no component suffix, a separator that was not derived from the components it is about to be written with. The message says which, and what to do about it.
It is a :class:ValueError because every one of those is a bad value, and it is named
so that a caller deciding whether a string is a chimera's name can catch exactly
this and nothing else.
Examples:
ChimeraDetails
dataclass
¶
What a chimera's completion record says about the build that produced it.
The Source a finished chimera records, in the details shape
:class:~genome.assembly.chimera_build.ChimeraBuilder writes — written and read here, so nothing
else has to know its keys. It answers the only question that decides whether an
assembly is a Chimera at runtime — the record, never the metadata row — and
carries the facts a later pass needs: the separator its chromosome names were written
with, and what each component and each contributed annotation was at build time.
Attributes:
| Name | Type | Description |
|---|---|---|
separator |
str
|
The run of underscores this chimera's chromosome names carry. |
component_details |
tuple of ComponentDetails
|
One entry per Component, in the sorted order the chimera's name spells them. |
Examples:
>>> details = ChimeraDetails(
... "__",
... (
... ComponentDetails("ce11", "1a2b3c", "wormbase_ws298", "4d5e6f"),
... ComponentDetails("ecHT115", "7a8b9c", None, None),
... ),
... )
>>> details.components
['ce11', 'ecHT115']
components
property
¶
The component assembly names, sorted — a fresh list each call.
merged_annotation
property
¶
The Registered name of the Merged annotation this build wrote, or None.
Read back rather than looked up: the name is the contributing annotations' names
joined by :func:merged_annotation_name, which is what spelled it when the merge
was registered. None when no component contributed one, which is a build that
registered no annotation at all rather than an empty one.
Examples:
as_details ¶
Return these details as a completion record writes them down.
The inverse of :meth:from_details. Kept to facts a later pass cannot re-derive
from the name alone: which spelling was used, what each component was when this
chimera was built, and — when there is a merged annotation — which of each
component's annotations went into it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
merged
|
bool
|
Whether this build registered a Merged annotation. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
The |
Examples:
from_record
classmethod
¶
Read a completion record's chimera details, or None when it has none.
:meth:from_details over the record's details, and None for an absent
record — the shape a caller holding a record rather than a payload wants. The
record names the assembly, so a broken one is refused in its own name without the
caller having to supply it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
CompletionRecord or None
|
The record to read, as :func: |
required |
Returns:
| Type | Description |
|---|---|
ChimeraDetails or None
|
The details, or |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the record claims to be a chimera's and cannot be read as one — see
:meth: |
Examples:
from_details
classmethod
¶
Read a completion record's details, or None when they are not a chimera's.
Two answers, and they are not the same answer. None means this is not a
chimera — the details of an ordinary downloaded or seeded assembly, which say
nothing about components at all. Details that do speak of components but cannot
be read as a build of this package's own writing are a broken registration,
and one raises rather than reading back as an ordinary assembly: both
:func:components_status and :func:~genome.assembly.download.verify_assembly decide
what to check from this answer, so a chimera silently demoted to an ordinary
assembly is one nothing ever compares against its components again.
A chimera's record spells the separator and the components together, and each
component entry names itself. Anything short of that — one key without the other,
either of the wrong type, an entry that is not an object or does not name an
assembly — is the broken case. The two annotation fields are the exception and
stay optional: a build that registered no merged annotation writes neither, and
both then read as None.
Taking the mapping rather than the record is what lets a caller that already holds
one — the CLI, whose register payload is the record — answer from what it has
instead of reading the same file again. assembly is what the refusal is
addressed to, since a mapping does not know whose it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
details
|
mapping of str to object
|
A registration record's |
required |
assembly
|
str
|
The assembly these details were recorded for, named in the refusal along with the command that repairs it. |
required |
Returns:
| Type | Description |
|---|---|
ChimeraDetails or None
|
The details, or |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If |
Examples:
ComponentDetails
dataclass
¶
ComponentDetails(
name: str,
sha256: str | None,
annotation: str | None,
annotation_sha256: str | None,
)
What a chimera's completion record says about one of its components.
One entry of the details shape :class:~genome.assembly.chimera_build.ChimeraBuilder writes,
read back. Every field is a fact about the component at the time this chimera was
built, taken from that component's own records rather than by rehashing anything —
which is what lets a later pass notice a component re-registered underneath the chimera.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The Component assembly name. |
sha256 |
str or None
|
The digest that component's completion record pinned, or |
annotation |
str or None
|
The Registered name of the annotation it contributed to the Merged
annotation, or |
annotation_sha256 |
str or None
|
That annotation's own recorded digest, or |
Examples:
as_entry ¶
Return this component's entry as a record writes it down.
The inverse of the reading :meth:ChimeraDetails.from_details does, so the keys
are spelled once. The two annotation keys are written only when a merged annotation
was registered: a build that registered none says nothing about annotations at all
rather than writing null beside every component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
merged
|
bool
|
Whether this build registered a Merged annotation. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
One entry of a completion record's |
Examples:
RegisteredAssembly
dataclass
¶
What preparing an assembly on disk produced: its record, and where that landed.
:func:register_assembly's answer — what genome assembly register prints, and what its
--json serializes — and :func:registered_assembly's, which reads it back without
preparing anything. The Completion marker the run wrote is the answer, so it
is carried whole rather than copied out field by field, and the two questions a surface
then asks — which files are claimed, and is this a Chimera — are answered from that
one record instead of by reading the directory again.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly that was registered, under the name the caller asked for. |
directory |
Path
|
Its Assembly dir — where those files and that record are. |
record |
CompletionRecord
|
The record the registration wrote, read back. |
Examples:
>>> from pathlib import Path
>>> from genome.store.completion import CompletionRecord
>>> registered = RegisteredAssembly(
... assembly="hg38",
... directory=Path("/data/genome/hg38"),
... record=CompletionRecord(
... kind="genome",
... name="hg38",
... files={"hg38.fa.fai": 21, "hg38.fa": 12},
... source_url="https://example.org/hg38.fa.gz",
... sha256="1a2b3c",
... tool_versions={},
... package_version="2026.8.0",
... completed_at="2026-08-12T09:00:00+00:00",
... details={},
... ),
... )
>>> registered.file_names
['hg38.fa', 'hg38.fa.fai']
>>> registered.chimera is None
True
>>> registered.genome_files.twobit
PosixPath('/data/genome/hg38/hg38.2bit')
>>> registered.as_json()["directory"]
'/data/genome/hg38'
>>> registered.as_json()["genome_files"]["fasta"]
'/data/genome/hg38/hg38.fa'
source_url
property
¶
Where the bytes were fetched from, or None when nothing was — a chimera's.
file_names
property
¶
Every file the record claims, sorted — a fresh list each call.
chimera
property
¶
What the build recorded about its components, or None for anything else.
The record is what says an assembly is a Chimera, here as everywhere else — and the record is already in hand, so a surface reporting the registration that just happened never reads the same file a second time to find out.
genome_files
property
¶
The four Genome files, where the Assembly dir layout puts them.
From the layout rather than :attr:record, whose files are sizes keyed by name.
as_json ¶
Return this registration as --json serializes it.
The record's own fields under the record's own names, then the assembly asked
for, the directory it landed in and its genome_files — the facts a record
does not hold about itself. No name is respelled here.
Returns:
| Type | Description |
|---|---|
dict
|
The record's fields, followed by |
UCSCGenomeDownloader ¶
UCSCGenomeDownloader(
assembly: str,
cache_dir: str | Path | None = None,
*,
metadata: AssemblyMetadata | None = None,
)
Bases: AssemblyRegistration
Download reference-genome FASTA files from the UCSC golden path.
UCSC serves per-assembly downloads under
https://hgdownload.soe.ucsc.edu/goldenPath/<assembly>/bigZips/. This
fetches the soft-masked, gzipped whole-genome FASTA
(<assembly>.fa.gz) and, by default, decompresses it to
<assembly>.fa.
An :class:~genome.assembly.registration.AssemblyRegistration whose FASTA arrives over
the network: the base owns the assembly's directory and the steps that finish a
registration in it, and everything added here is about where the bytes come from
— the URL, the name check, the pinned digest. The fetch itself is
:func:~genome.store.fetch.fetch_url, given this assembly's working area: where a
download lands is a decision the assembly's directory already made, so nothing here
binds it to a cache of its own.
The assembly's metadata row is consulted first: when it pins a source URL that URL is fetched instead of the derived golden-path one, and when it pins a sha256 the unpacked FASTA is checked against it. An assembly the table does not list keeps working exactly as before — the table is a cross-reference, not an allow-list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
UCSC assembly name, e.g. |
required |
cache_dir
|
str or Path
|
Override the storage directory. Defaults to
:func: |
None
|
metadata
|
AssemblyMetadata
|
A complete metadata record to use instead of the curated table's row for
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
metadata |
AssemblyMetadata
|
The record this downloader works from — the one passed in, else what
:func: |
Examples:
>>> dl = UCSCGenomeDownloader("hg38")
>>> dl.fasta_url
'https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz'
>>> UCSCGenomeDownloader("no_such_assembly").metadata.sha256 is None
True
>>> fasta = dl.fetch_fasta()
fasta_url
property
¶
URL of the gzipped whole-genome FASTA — the pinned source, else the golden path.
A metadata row that pins a source URL answers this outright; otherwise the URL is derived from the assembly name and UCSC's golden-path layout, as it always was.
validate_assembly ¶
Check that assembly is a real golden-path directory at UCSC.
Sends an HTTP HEAD to :attr:assembly_url so a typo in the assembly
name fails fast with a clear message, rather than surfacing later as an
opaque 404 on the FASTA file itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
Seconds to wait for the server before giving up. |
30.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If no directory exists for |
RequestException
|
If the request fails for any other reason (network error, timeout, or an unexpected non-success status). |
Examples:
verify_fasta ¶
Return the sha256 of the unpacked FASTA, raising when it is not the pinned one.
The digest is taken over the unpacked <assembly>.fa, never over the
.fa.gz it arrived in, which is why pooch's own known_hash cannot do this
job: pooch hashes what it downloaded. Gzip bytes change under recompression
while the FASTA inside does not, so a content digest also matches a copy taken
from a mirror or handed over by hand. The file is streamed, so a
whole-genome FASTA is never held in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fasta
|
Path
|
The file to check. Defaults to this assembly's |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The computed hex digest. An assembly whose metadata pins no sha256 — or that the table does not list — has nothing to disagree with, so the value is simply reported back for recording or for pinning later. |
Raises:
| Type | Description |
|---|---|
ChecksumMismatchError
|
If the metadata pins a sha256 and the file's digest is a different one; the message names both values. |
FileNotFoundError
|
If the file does not exist. |
Examples:
fetch_fasta ¶
fetch_fasta(
*,
known_hash: str | None = None,
decompress: bool = True,
progressbar: bool = True,
) -> Path
Download (and optionally decompress) the genome FASTA into the working area.
Both files land in the assembly's working area rather than beside its prepared
files: nothing there is claimed by a completion record, and the whole area is
discarded once one is written. :meth:fetch_genome is what moves the unpacked
FASTA out of it.
When the URL had to be derived from the assembly name, the assembly is first
confirmed to exist at UCSC via :meth:validate_assembly, so a typo fails fast
with a clear message. When the metadata row pins a source URL that check is
skipped: validation is a property of the source, and a pinned URL
is the source, so there is nothing left to guess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
known_hash
|
str
|
Expected hash of the downloaded |
None
|
decompress
|
bool
|
If |
True
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
Returns:
| Type | Description |
|---|---|
Path
|
Path inside the working area to the decompressed |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the URL was derived and the assembly is unknown to UCSC. |
fetch_genome ¶
fetch_genome(
*,
known_hash: str | None = None,
progressbar: bool = True,
overwrite: bool = False,
) -> GenomeFiles
Prepare the reference genome in one call — by download, or by concatenation.
The choke point every way in funnels through, and therefore where a name is
resolved: :func:~genome.assembly.source.resolve_source says whether this assembly's
FASTA is fetched or concatenated from components already on this disk, and only the
first of those is what the rest of this method does. A
:class:~genome.assembly.source.ComponentSource is handed to
:func:~genome.assembly.chimera_build.build_chimera instead, which is why genome
assembly register <name> --force is one command for all three kinds of Source
rather than a download that fails on two of them.
A finished registration is returned from its record either way, and a chimera's components are checked against their own records as it is handed back — the one failure a digest of this assembly's own bytes cannot show, since a component registered again underneath leaves those bytes untouched and no longer a copy of anything that exists. An assembly with no components pays nothing for the question.
Chains :meth:fetch_fasta, :meth:verify_fasta and
:func:genome.assembly.fasta.prepare_fasta: download <assembly>.fa.gz from the
assembly's source into the working area, decompress it, check the unpacked FASTA
against the sha256 its metadata pins, move it to :attr:cache_dir, then build
the .fai index, .2bit encoding, and .chrom.sizes beside it
(<LIULAB_DATA>/genome/<assembly>/ by default).
A completion record is written last, once all four files exist, holding the URL fetched, the digest computed, every file with its size, the tool versions, the package version and the time. That record is what makes a later call cheap: it is read, its claims are checked against disk by size alone, and nothing is fetched. The archive is deleted with the rest of the working area at that point — and only then, so an interrupted run still repairs from it.
A directory that cannot be trusted — files with no record, or a record that
disagrees with what is on disk — raises rather than being rebuilt or
trusted, naming genome assembly register <assembly> --force. That is what
overwrite=True is: it skips the question, keeps the unpacked FASTA when its
digest can be shown to be the pinned one, and fetches the source again when it
cannot (see :meth:_proven_fasta). An absent or empty directory is not a broken
state — it is a fresh registration and proceeds normally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
known_hash
|
str
|
Expected hash of the downloaded |
None
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
overwrite
|
bool
|
Register again from scratch: the assembly's completion record is not consulted, and the preparation steps (faidx, 2bit, chrom.sizes) rerun even when their outputs look fresh. An archive still sitting in the working area is reused rather than downloaded again. |
False
|
Returns:
| Type | Description |
|---|---|
GenomeFiles
|
Paths to the decompressed FASTA and its three derived files. |
Raises:
| Type | Description |
|---|---|
UnfinishedRegistrationError
|
If the assembly directory holds files but no record. |
RegistrationMismatchError
|
If its record disagrees with what is on disk, or if a component of this chimera was registered again since it was built. |
FileNotFoundError
|
If the name resolves to a chimera whose components are not all prepared here, or spells them in an order that is not the canonical one. Both messages name the command to run instead. |
HTTPError
|
If the download fails (e.g. a wrong assembly name 404s). |
ValueError
|
If the assembly is unknown to UCSC, or if |
ChecksumMismatchError
|
If the metadata pins a sha256 and the unpacked FASTA is not it. |
ToolNotFoundError
|
If |
RuntimeError
|
If any native preparation tool exits non-zero. |
Examples:
Skipped, since it downloads a genome: the one call decompresses and prepares it too.
fetch_genome_from ¶
fetch_genome_from(
source: str | Path,
*,
progressbar: bool = True,
overwrite: bool = False,
) -> GenomeFiles
Prepare the genome from a user-provided FASTA instead of downloading from UCSC.
Use this to seed an assembly from a file you already have or a non-UCSC
URL — handy when the UCSC golden path is unreachable (firewall/proxy) or
for a custom reference. source is either a local filesystem path
(copied into the working area) or a URL (fetched with
:func:~genome.store.fetch.fetch_url, so http(s), ftp and sftp all work). Gzipped sources
(.gz) are decompressed. The resulting <assembly>.fa is then
indexed/2bit/chrom.sizes-prepared exactly as :meth:fetch_genome does, and a
completion record is written last, recording the source it was given and the
digest of what arrived. UCSC is never contacted. Neither is the metadata row's
pinned source or checksum: a seeded FASTA is whatever the caller handed over —
it is recorded, never compared — and the assembly name is only a label for the
directory it lands in.
The :class:~genome.assembly.source.SeededSource kind, and the one of the three nothing
resolves: the caller answered before anything was asked, so no name is read and no
record is consulted for what this assembly is. Its record is still what says
whether the work is already done — that check runs first, exactly as it does for a
name that had to be resolved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str or Path
|
Local FASTA path or http(s)/ftp/sftp URL. |
required |
progressbar
|
bool
|
Show a download progress bar while fetching a URL (ignored for a local copy). |
True
|
overwrite
|
bool
|
Re-read the source and rerun preparation even when this assembly's completion record already says it is registered. |
False
|
Returns:
| Type | Description |
|---|---|
GenomeFiles
|
Paths to the prepared FASTA and its three derived files. |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the assembly directory holds files but no record, or a record that
disagrees with what is on disk. The message names this same call as the
repair — |
FileNotFoundError
|
If |
ValueError
|
If |
ToolNotFoundError
|
If a preparation tool is not on |
RuntimeError
|
If any native preparation tool fails. |
VerifiedAssembly
dataclass
¶
VerifiedAssembly(
assembly: str,
fasta: Path,
sha256: str,
expected: str | None,
expected_from: str | None,
components: str | None,
)
What re-reading a FASTA proved: its digest, what that was held to, and the components.
:func:verify_assembly's answer, and three results a caller must be able to tell
apart, so each is a field of its own: the digest computed, what supplied the digest
it was held to — being held to the lab's pin and being held to what this machine last
produced are different answers — and, for a Chimera, what comparing its components
settled. A digest that disagreed raises rather than arriving here, so this is what
nothing refused.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The Assembly whose row supplied the digest to check against. |
fasta |
Path
|
The file that was read. |
sha256 |
str
|
The digest computed over it. |
expected |
str or None
|
The digest it was held to, or |
expected_from |
str or None
|
What answered with |
components |
str or None
|
:data: |
Examples:
>>> from pathlib import Path
>>> checked = VerifiedAssembly(
... assembly="sacCer3",
... fasta=Path("/data/genome/sacCer3/sacCer3.fa"),
... sha256="6ff72f07",
... expected="6ff72f07",
... expected_from=EXPECTED_FROM_TABLE,
... components=None,
... )
>>> checked.verified
True
>>> checked.as_json()["expected_from"]
'table'
verified
property
¶
Whether there was a digest to check against at all, rather than merely one computed.
as_json ¶
Return this verification as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
Every attribute above, with |
GenomeFiles
dataclass
¶
A FASTA together with its derived index and companion files.
Attributes:
| Name | Type | Description |
|---|---|---|
fasta |
Path
|
The source FASTA file. |
fai |
Path
|
The |
twobit |
Path
|
The 2bit-encoded sequence. |
chrom_sizes |
Path
|
Two-column |
AssemblyMetadata
dataclass
¶
AssemblyMetadata(
assembly_name: str,
species: str | None,
ucsc_name: str | None,
ncbi_name: str | None,
ncbi_assembly_id: str | None,
ncbi_taxid: int | None,
source_url: str | None = None,
sha256: str | None = None,
intron_length_cap: int | None = None,
intron_length_cap_rationale: str | None = None,
)
Identifiers for one reference assembly (one row of the metadata table).
The single declaration of what an assembly's metadata consists of: the table
is parsed through these fields, and a complete record is what
:class:~genome.assembly.genome.Genome accepts in place of the table's own row.
Only assembly_name is required. Every other column may be left blank, and a
blank cell reads back as None rather than as text: the table fills in over
time, and a freshly prepared assembly pins its source and digest well before
anyone supplies its species, its UCSC and NCBI names or its taxonomy id.
source_url and sha256 are what makes preparing an assembly reproducible.
source_url pins where its FASTA is fetched from, so nothing has to be derived
or guessed; sha256 pins the digest of the unpacked FASTA that source
yields — not of the compressed archive it arrives in, so a copy taken from a
mirror or recompressed elsewhere still matches. A row with no digest is
unverified rather than wrong.
intron_length_cap is the longest gap a spliced aligner should take for an
intron on this assembly, and intron_length_cap_rationale says why that number
and not another. It is a deliberately loose round number set by hand, never
computed from an annotation — an annotation catalogues the transcripts someone
observed, so its longest intron is a floor on what the organism does rather than a
ceiling on it. Nothing in this package reads either field: they are
curated here so that a consumer choosing aligner parameters reads a fact about the
assembly from the same row as its identifiers. A blank cap is an assembly nobody
has characterised, which is legal and says no bound has been chosen — the reading
that leaves such an assembly aligning exactly as it did before.
A Chimera's row pins neither, and that is deliberate rather than pending: its
bytes are not fetched from anywhere, and they are derived by a pure function from
components whose own rows are pinned, so it is proven transitively. The table pins
what was downloaded; what was derived is pinned by a test, where a change in this
package's own concatenation code belongs. Such a row carries its name and
nothing else — its identifiers are its components', reachable through
:attr:~genome.assembly.genome.Genome.chrom_components — and it exists so that a machine
holding none of them can still tell a chimera's name from a local key someone chose.
ucsc_name is blank permanently rather than pending in some rows, for the same
reason the assembly id is a local key rather than a UCSC one: the lab
supports references UCSC has never carried, and such a row simply has no name in
that namespace to give.
Examples:
>>> record = AssemblyMetadata(
... "hg38", "Homo sapiens", "hg38", "GRCh38", "GCF_000001405.40", 9606
... )
>>> record.species
'Homo sapiens'
>>> record.sha256 is None # nothing pinned unless the row says so
True
unknown
classmethod
¶
Return the record for an assembly the table does not list: the name, and nothing else.
What unlisted looks like as a record rather than as a missing one. Every
identifier is genuinely unknown, which a blank cell already means everywhere
else, so a caller reads a field and gets None instead of first asking
whether there is a record to read it off. It is exactly the line
:func:format_table_row emits for an assembly nobody has curated yet.
The name is the local key the caller asked for, since that is the only
identifier an unlisted assembly has — the assembly id is a local key and the
table is a cross-reference rather than an allow-list. This answers
what is known about this assembly; whether the table lists it at all is
:func:lookup_assembly's question and stays a separate one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_name
|
str
|
The assembly the record is for. |
required |
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
A record carrying |
Examples:
from_row
classmethod
¶
Build a record from one row of a metadata table.
The reader half of the register-then-paste flow :func:format_table_row writes:
a row is a mapping of column name to cell, which is how the shipped TSV is read
and how :func:dataclasses.asdict of a record spells one. Each column is parsed
by its own declared type, and a blank cell — empty, absent, or the NaN pandas
reads a blank as — means unknown, which only a column that has an unknown takes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Column name to cell. A cell is the table's own text, but a value already of
the column's type is taken as it stands, so a record's fields are a row.
Keys outside :data: |
required |
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
The record the row spells. |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
If a cell cannot be read as its column's type, or a column that has no unknown is blank. The record is built from parsed cells or not at all, so a caller is handed a whole record or an error naming the column — never a record carrying the columns that happened to come before the bad one. |
Examples:
AssemblyDir
dataclass
¶
One Assembly's directory, and everything the layout puts inside it.
The Assembly dir as a value rather than a rule applied again at each call site.
Where an assembly lives is decided once — by :meth:locate, which is the only
implementation of an explicit directory overrides the Data dir layout — and
the answer is then carried, so a caller holding one cannot resolve it differently
from the caller that opened it. That is not hypothetical: an index derived from the
data root rather than from the assembly it indexes lands beside a different
assembly's files, and reads a completion record that is not there.
Every subtree an assembly owns is spelled here, so the layout is legible in one
place: the working area, the gtf/ subtree the Annotation context files into, the
index/ subtree the Index context files into, and the four Genome files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly name — the key this directory is addressed by, and the name its files carry. |
required |
path
|
Path
|
The directory itself. |
required |
Examples:
>>> import os
>>> os.environ["LIULAB_DATA"] = "/scratch/liulab"
>>> here = AssemblyDir.locate("hg38")
>>> here.path
PosixPath('/scratch/liulab/genome/hg38')
>>> here.index_dir("chromap")
PosixPath('/scratch/liulab/genome/hg38/index/chromap')
>>> here.sibling("mm39").path
PosixPath('/scratch/liulab/genome/mm39')
>>> del os.environ["LIULAB_DATA"]
work_dir
property
¶
The disposable working area a build stages in — see :func:~genome.store.completion.work_dir.
record_path
property
¶
Where this assembly's Completion marker is written, whether or not it exists.
is_registered
property
¶
Whether a record here vouches for this assembly — the one spelling of that rule.
By record alone: a directory holding files but no record is not registered,
because nothing vouches for them, and nothing is read but the record itself. That
is what :func:~genome.assembly.source.is_prepared asks of a name, and what
listing the tree asks of every directory in it, so the two cannot answer
differently. Whether the files a record claims are still what it claims is a
separate and more expensive question — genome assembly verify owns it.
Examples:
annotations_root
property
¶
The gtf/ subtree, parent of every annotation directory.
genome_files
property
¶
The four Genome files this assembly's preparation produces, existing or not.
Every way of producing the FASTA materializes it as <assembly>.fa and derives
identically named companions, so one layout describes them all.
locate
classmethod
¶
Return where assembly lives: cache_dir when given, else the layout's answer.
The one place the override rule is written. cache_dir is the directory
itself and not a root to file under, which is what lets a caller put one
assembly somewhere of its own without inventing a second Data dir.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to locate. |
required |
cache_dir
|
str or Path
|
An explicit Assembly dir, overriding the layout. |
None
|
Returns:
| Type | Description |
|---|---|
AssemblyDir
|
Where that assembly's files belong. Nothing is created and nothing is read. |
Examples:
sibling ¶
Return another assembly's Assembly dir, found beside this one.
How an assembly named in a record is found again — a Chimera's components,
say. A record carries names and never paths, which is what keeps a registered
directory movable, so the name has to be resolved against something: this
resolves it against where the asking assembly is, rather than against the
Data dir the process happens to be pointed at. Under the ordinary layout the
two agree, since every assembly is a sibling under <data dir>/genome/. They
part company exactly when one assembly was placed somewhere of its own, and then
beside-me is the answer that finds anything at all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The other assembly's name. |
required |
Returns:
| Type | Description |
|---|---|
AssemblyDir
|
Its directory, beside this one. Nothing is created and nothing is read. |
Examples:
read_record ¶
Return this assembly's completion record, or None when it has none.
annotation_dir ¶
Return the directory the annotation registered as name is filed under.
completed_files ¶
Return the prepared Genome files when the record vouches for them, else None.
Is this assembly finished here?, asked of the directory itself so that a caller
holding one — a verification, say — needs no registration object to find out. The
completion record is the only thing consulted: it must be there, and every file it
claims must be present at the size it claims. That is one stat per file and no
file contents, so reopening a prepared genome is instant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
repair
|
str
|
The command quoted into the refusal a directory that cannot be trusted raises. |
required |
Returns:
| Type | Description |
|---|---|
GenomeFiles or None
|
The four files, or |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the directory holds files with no record, or a record that disagrees with
what is on disk (see :func: |
Examples:
AssemblyRegistration ¶
One assembly's directory, and the steps that finish a registration in it.
An assembly name plus the directory its files belong in, and everything a registration does against that pair whatever produced the FASTA: is it already registered, which paths does it own, where does the work happen, what repairs it, how does the FASTA get into place, and what does the record say afterwards. Subclass it and add the step that produces the FASTA.
Every step here is a private one. The class is the seam, not a public surface: what
a caller registers an assembly with is
:func:~genome.assembly.download.register_assembly or :class:~genome.assembly.genome.Genome.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly name — the key its directory is addressed by, and the name its files and its record carry. |
required |
cache_dir
|
str or Path
|
Override the storage directory. Defaults to
:func: |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
assembly |
str
|
The assembly name passed at construction. |
cache_dir |
Path
|
The Assembly dir this registration fills. |
Examples:
>>> from pathlib import Path
>>> registration = AssemblyRegistration("hg38", Path("/scratch/hg38"))
>>> registration._repair_command()
'genome assembly register hg38 --force'
AssemblyStatus
dataclass
¶
What the curated table offers, set against what the assembly tree holds here.
:func:assembly_status's answer, and what genome assembly list prints. It is the
first question a new user has — genome assembly register is the first command
anyone runs and nothing else in the CLI says what may follow it — and the question
somebody landing on a machine they did not set up has: what is already prepared on it.
Attributes:
| Name | Type | Description |
|---|---|---|
directory |
Path
|
The assembly tree's root, whether or not anything is there. |
assemblies |
tuple of AssemblyStatusRow
|
One row per name: the offered ones in table order, then whatever this tree holds that no row lists, in name order. |
Examples:
>>> status = assembly_status(root="/tmp/definitely-not-a-data-root")
>>> status.registered
()
>>> status.as_json()["directory"]
'/tmp/definitely-not-a-data-root'
registered
property
¶
The assemblies a record here vouches for, in the order they are reported.
What do I actually have on this machine — the answer as data rather than as printed lines, for a caller who imports this instead of running the command.
summary
property
¶
The closing line: what is registered here, and the command that changes it.
Two answers, and both name what to run next: nothing is registered, which is a
fresh install's ordinary state and needs the command that prepares one; or
something is, and then a reader who doubts one has a command for that too —
verify is where integrity is settled, and is named here because this report
deliberately does not settle it.
Returns:
| Type | Description |
|---|---|
str
|
One line, beginning |
unregistered_note
property
¶
What a directory nothing vouches for means, when the tree holds one; else None.
Absent when there is nothing to explain, so the ordinary listing does not carry a sentence about a state nothing is in.
Returns:
| Type | Description |
|---|---|
str or None
|
One line about the |
as_json ¶
Return this report as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
The tree's |
AssemblyStatusRow
dataclass
¶
AssemblyStatusRow(
assembly_name: str,
offered: bool,
registered: bool,
present: bool,
directory: str | None,
species: str | None,
ucsc_name: str | None,
ncbi_name: str | None,
source_url: str | None,
sha256: str | None,
)
One assembly, in whichever of its states it is: offered, registered, or merely here.
One shape for all of them, so a reader never has to ask which fields a row has — a
name the table does not list carries the table's columns as None, and one nothing
is prepared for carries no :attr:directory.
:attr:registered and :attr:present are not independent: a record lives in the
directory it vouches for, so a registered assembly is always present, and the pair
that needs saying out loud is the other one — present and not registered. That is
the case enumerating the tree meets and asking after a single name never did: a
directory that is neither a good registration nor absent. Reporting it as absent lies
to somebody looking at a full disk, and reporting it as an assembly lies about what is
trustworthy, so it is reported as what it is and :attr:state says so in three words.
Attributes:
| Name | Type | Description |
|---|---|---|
assembly_name |
str
|
The assembly this row is about — the key its directory is addressed by. |
offered |
bool
|
Whether the curated table lists it. |
registered |
bool
|
Whether a record here vouches for it. |
present |
bool
|
Whether a directory for it is in the assembly tree, registered or not. |
directory |
str or None
|
Its Assembly dir, when one is there; |
species |
str or None
|
The species the table names; |
ucsc_name |
str or None
|
Its name in UCSC's namespace, from the table; |
ncbi_name |
str or None
|
Its name in NCBI's namespace, from the table; |
source_url |
str or None
|
Where the table says its FASTA is fetched from; |
sha256 |
str or None
|
The digest the table pins for the unpacked FASTA; |
Examples:
>>> row = AssemblyStatusRow(
... assembly_name="hg38",
... offered=True,
... registered=False,
... present=False,
... directory=None,
... species="Homo sapiens",
... ucsc_name="hg38",
... ncbi_name="GRCh38",
... source_url="https://example.org/hg38.fa.gz",
... sha256=None,
... )
>>> row.state
'offered, not registered'
>>> row.as_json()["offered"]
True
state
property
¶
Which of its states this row is in, in the words a surface prints.
registered first, because it is the strongest thing that can be said and it
settles the two weaker ones: a registered assembly is present, and whether the
table also offers it is the only distinction left. here, not registered comes
next for the same reason it exists at all — a directory is there, and answering
with what the table says about the name would leave the reader to discover the
directory some other way.
Returns:
| Type | Description |
|---|---|
str
|
One of |
as_json ¶
Return this row as --json serializes it: every attribute above, in order.
:attr:state is not among them: it is read from :attr:offered,
:attr:registered and :attr:present, which are all here, so writing it out too
would be a second spelling of the same rule for a reader to disagree with.
Returns:
| Type | Description |
|---|---|
dict
|
The row's fields, under their own names. |
TwoBit ¶
An open UCSC 2bit file, queried in 0-based half-open coordinates.
The handle is opened at construction and held until :meth:close (or the
context-manager exit). Reuse a single instance for many queries rather than
re-opening per lookup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
Path to a |
required |
masked
|
bool
|
Preserve soft-masking: lower-case bases for repeat-masked regions. When
|
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Path
|
The 2bit file path. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
RuntimeError
|
If |
Examples:
sequence ¶
Return the sequence of chrom (or a sub-range) as a plain string.
Coordinates are bounds-checked against the chromosome length: an end
past the end of the sequence raises rather than being silently clamped
(py2bit's default behavior).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chrom
|
str
|
Sequence name as stored in the file. |
required |
start
|
int
|
0-based start, inclusive. Defaults to the start of the chromosome. |
None
|
end
|
int
|
0-based end, exclusive (half-open). Defaults to the end of the chromosome. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The bare sequence, case preserved when the file was opened with
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
nocheck_sequence ¶
Return the sequence of chrom (or a sub-range) without bounds-checking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chrom
|
str
|
Sequence name as stored in the file. |
required |
start
|
int
|
0-based start, inclusive. Defaults to the start of the chromosome. |
None
|
end
|
int
|
0-based end, exclusive (half-open). Defaults to the end of the chromosome. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The bare sequence, case preserved when the file was opened with
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
__exit__ ¶
__exit__(
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None
Close the handle on context-manager exit.
derive_name ¶
Derive a chimera's assembly name from the names of its components.
The name is the component names sorted lexicographically and joined by _. It is
derived rather than given and cannot be overridden: one set of components means
exactly one name, and therefore one directory and one index, whatever order the
caller happened to list them in.
A chimera's own name always carries a _, and a component's name never may, so
this is also where a chimera is refused as a component of another chimera. That is
only the spelling half of the rule: whether a prepared assembly is itself a chimera
is answered by the record on its disk, which this module cannot see.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
components
|
Iterable[str]
|
Two or more component assembly names, in any order. Each must match
|
required |
Returns:
| Type | Description |
|---|---|
str
|
The derived assembly name. |
Raises:
| Type | Description |
|---|---|
ChimeraNamingError
|
If fewer than two components are given, a component repeats, or a component name is not alphanumeric. |
Examples:
split_name ¶
Split a chimera's assembly name back into its candidate component names.
The inverse of :func:derive_name, and syntactic only: it says that name is
spelled the way a chimera's name is spelled, never that those components exist.
Deciding that belongs to the caller, which asks whether each candidate is prepared on
this machine or listed in the shipped table — the step that separates ce11_ecHT115
from an ordinary assembly someone happened to call my_ref.
Candidates come back in the order the name spells them, not sorted, so that a caller
can hand them straight to :func:derive_name and compare: a name whose components are
real but mis-ordered is detectable, and the canonical spelling can be named back.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
An assembly name to read as a chimera's. |
required |
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
The candidate component names, in the order |
Raises:
| Type | Description |
|---|---|
ChimeraNamingError
|
If |
Examples:
split_suffixed ¶
Split a suffixed chromosome name back into (chromosome, component).
The split is at the last run of the separator, which is unconditionally the right
one: what follows the suffix is an alphanumeric component name, so no later run of the
separator can exist to be mistaken for it. A component that already spells a
chromosome bar_ce11 therefore still reads back correctly — bar_ce11__ecHT115
is ('bar_ce11', 'ecHT115').
separator defaults to '__', which is what a chimera derives whenever no
component carries a doubled underscore of its own — every assembly the lab ships.
When one does, the chimera's record is the authority on its separator and the caller
passes it. Writing has no such default, on purpose: see :func:suffixed.
The same contract in the form to hand a tool that cannot import this module is the
regex the Assembly glossary publishes, which :func:_suffix_pattern generates and a
test holds against this function name by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
A suffixed chromosome name. |
required |
separator
|
str
|
The run of underscores this chimera recorded. |
``"__"``
|
Returns:
| Type | Description |
|---|---|
tuple[str, str]
|
|
Raises:
| Type | Description |
|---|---|
ChimeraNamingError
|
If |
Examples:
suffixed ¶
Spell one chimera chromosome name: the chromosome, the separator, the component.
Unconditional — a chromosome is suffixed whether or not another component carries the same name — so attribution is the same operation for every name in the reference and no mapping of any kind has to be stored to perform it.
separator is required and deliberately has no default, unlike
:func:split_suffixed: it belongs to one chimera, comes from
:func:derive_separator, and a build that wrote a constant instead would quietly lose
the self-announcing property the derivation exists to preserve, in a way no round trip
can detect.
An empty chromosome is not refused, and gives back the tail alone: that is how
:func:~genome.annotation.registration.register_merged_gtf spells the suffix it appends to a whole
file's seqnames, validating the component and the separator once rather than per line.
It is the one thing this function returns that is not a chromosome name, and
:func:split_suffixed refuses to read it back as one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chromosome
|
str
|
The chromosome name as the component itself spells it. |
required |
component
|
str
|
The component assembly name, alphanumeric. |
required |
separator
|
str
|
The run of underscores this chimera derived. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The suffixed chromosome name. |
Raises:
| Type | Description |
|---|---|
ChimeraNamingError
|
If |
Examples:
components_status ¶
Check every component of the assembly in assembly_dir, and report the answer.
The one failure a digest of a chimera's own bytes cannot show. Those bytes are a copy of its components', so a component registered again underneath it leaves the chimera intact, agreeing with its own record, and no longer a copy of anything that exists — silently stale sequence, and stale gene models one level down. Both are caught here, and both are record against record: the digests this chimera wrote down are compared against the ones the components' own records pin now, so this reads a handful of small JSON files and not one base of sequence.
One entry point for two needs, because they are one comparison. A component proved to
have changed raises, wherever the question was asked from — reopening a finished
chimera, rebuilding one, verifying one. What is returned is what nothing raised over,
and a caller that only wants the refusal ignores it: a comparison that could not be
made is :data:COMPONENTS_UNKNOWN and is not a pass, and a surface silent about it
would say exactly what it says when everything agreed.
An assembly with no components recorded has nothing to compare and returns at once, which is what makes an ordinary assembly pay nothing rather than be asked about. Likewise an absent digest on either side, which means unknown rather than wrong: a component that pinned none, or an annotation registered before its digest was recorded, leaves that component unguarded rather than refused.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly_dir
|
AssemblyDir
|
The Assembly dir the chimera was built in. It carries the assembly's name, quoted in the error along with the command that repairs it, and it is what each component is found beside. |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
:data: |
Raises:
| Type | Description |
|---|---|
RegistrationMismatchError
|
If a component's FASTA, or the annotation it contributed to the Merged
annotation, is not the one this chimera was built from. The message names both
digests and |
RegistrationError
|
If the record claims to be a chimera's and cannot be read as one — see
:meth: |
Examples:
read_chimera_details ¶
Return the chimera details recorded in directory, or None when it has none.
:meth:ChimeraDetails.from_record over :func:~genome.store.completion.read_record —
the one call that answers is the assembly registered here a chimera?, and the only
thing :attr:genome.assembly.genome.Genome.components consults.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
Path
|
An Assembly dir — the directory a registration filled. |
required |
Returns:
| Type | Description |
|---|---|
ChimeraDetails or None
|
The details, or |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the record there claims to be a chimera's and cannot be read as one — see
:meth: |
Examples:
assembly_table_row ¶
assembly_table_row(
assembly: str,
*,
cache_dir: str | Path | None = None,
progressbar: bool = True,
) -> AssemblyMetadata
Fetch assembly's FASTA and return the metadata table row describing it.
What makes filling in the table's checksum column a copy-paste rather than a manual
hashing chore: the FASTA is downloaded and unpacked, its sha256 is computed over the
unpacked file, and the assembly's row comes back with source_url set to the
URL that was actually fetched and sha256 to that digest. Every other field is
the curated table's own — or blank for an assembly the table does not list, since
those identifiers are ones only a person can supply.
Only the FASTA is fetched: no .fai, .2bit or chrom.sizes is built, so
this needs no native tools. Nothing is registered either — both the download and its
unpacked form stay in the assembly's working area, so hashing a genome for the table
never leaves an unregistered FASTA among that assembly's own files, and re-running it
reuses what is already there.
A Chimera is refused before anything is fetched, because it has no work here to do: its row pins nothing, so there is no digest to compute and no source to record, and the refusal describes that row rather than printing it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to fetch, e.g. |
required |
cache_dir
|
str or Path
|
Override which assembly directory the download works in. Defaults to
:func: |
None
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
The row itself, with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the URL had to be derived and the assembly is unknown to UCSC, or if the name resolves to a chimera, which has no row to compute. |
FileNotFoundError
|
If the name spells a chimera's components in an order that is not the canonical one; the message names the spelling to use. |
Notes
The digest is reported, never enforced. A row that already pins one is not
consulted, because this is the command to reach for when an upstream file has
legitimately changed and the pin has to be regenerated — refusing on a mismatch
would refuse exactly when the command is needed. Checking a FASTA you already
hold against the official row is
:meth:UCSCGenomeDownloader.verify_fasta's job, not this one's.
Examples:
register_assembly ¶
register_assembly(
assembly: str,
*,
source: str | Path | None = None,
force: bool = False,
cache_dir: str | Path | None = None,
progressbar: bool = True,
metadata: AssemblyMetadata | None = None,
) -> RegisteredAssembly
Prepare assembly on disk and return the record of what that did.
Naming an assembly is enough: where its FASTA comes from and which digest it must match are the metadata table's to know. The whole pipeline runs — fetch, unpack, verify, index, derive — and the completion record lands last. An assembly that is already registered is returned from its record without fetching anything.
A directory that cannot be trusted raises instead (see :meth:UCSCGenomeDownloader.fetch_genome); force=True is what repairs one, and it
keeps an unpacked FASTA it can prove is the pinned one rather than downloading a
whole genome again.
The name is the whole interface, chimeras included: a name whose parts are a
prepared or listed assembly each is concatenated from those components instead of
being fetched, so this one call prepares all three kinds of Source and force
repairs all three. See :func:~genome.assembly.source.resolve_source for the order the
checks run in and what each one settles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to register, e.g. |
required |
source
|
str or Path
|
Seed the assembly from this FASTA — a local path or an http(s)/ftp/sftp URL —
instead of fetching the source its metadata pins. See
:meth: |
None
|
force
|
bool
|
Register again from scratch, repairing a directory that raises. |
False
|
cache_dir
|
str or Path
|
Override which directory the assembly is registered in. Defaults to
:func: |
None
|
progressbar
|
bool
|
Show a download progress bar (requires |
True
|
metadata
|
AssemblyMetadata
|
A complete metadata record to use instead of the curated table's row. |
None
|
Returns:
| Type | Description |
|---|---|
RegisteredAssembly
|
The completion record the run wrote — |
Raises:
| Type | Description |
|---|---|
RegistrationError
|
If the directory holds a build that cannot be trusted as finished, or (with
|
FileNotFoundError
|
If the name resolves to a chimera this machine cannot build — a component that is not prepared here, or the components in an order that is not the canonical one. |
ChecksumMismatchError
|
If the metadata pins a sha256 and the unpacked FASTA is not it. |
ToolNotFoundError
|
If |
Examples:
registered_assembly ¶
Return assembly's registration as it stands on disk, preparing nothing.
The check reopening a registered assembly makes, and nothing registering one does: the
Completion marker must be there, every file it claims present at its size, and a
Chimera's components unchanged. Nothing is fetched, no External tool runs and no
directory is created. Whether the bytes are intact is :func:verify_assembly's question.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to read back, e.g. |
required |
cache_dir
|
str or Path
|
Override which directory the assembly is registered in. Defaults to
:func: |
None
|
Returns:
| Type | Description |
|---|---|
RegisteredAssembly
|
The answer :func: |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If nothing is registered there: the directory is absent, or holds nothing an
assembly's record would claim. The message names |
RegistrationError
|
If the directory holds a build that cannot be trusted as finished, or a chimera
whose component was registered again since it was built. The message names
|
Examples:
verify_assembly ¶
verify_assembly(
assembly: str,
*,
fasta: str | Path | None = None,
cache_dir: str | Path | None = None,
metadata: AssemblyMetadata | None = None,
) -> VerifiedAssembly
Re-read a FASTA and check its sha256 against the digest expected of it.
The one operation that reads bytes rather than sizes. Registering an assembly and reopening it both go by presence and size, which is what makes them instant; this is the deliberate re-verification for when integrity is actually in doubt, and it costs a full pass over the file.
What is expected of it comes from the assembly's curated row, and failing that,
from the completion record its own registration wrote — a fallback rather than a
question about what kind of assembly this is (see :func:_expected_digest).
expected_from says which answered, because being held to a pinned digest and being
held only to what this machine last produced are different results and a caller must be
able to tell them apart.
With no fasta it verifies the assembly's own registered FASTA, and the
registration must be intact — a directory that cannot be trusted raises here as it
does anywhere else. Two things are then checked beside the digest, both by reading
records rather than bytes: that each component this assembly was built from is still
the one it was built from, and that each annotation merged into its own is still the
one that was merged. Neither costs an assembly without components anything. Point
fasta at any file to check that instead: a copy taken from a mirror or handed
over by hand is checkable before anything is built on it, and nothing needs to be
registered first — nothing is then asked about components, since the assembly's own
registration is not what is being verified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly whose row supplies the digest to check against, e.g. |
required |
fasta
|
str or Path
|
A FASTA to check instead of the assembly's registered one. |
None
|
cache_dir
|
str or Path
|
Override which directory the assembly is registered in. |
None
|
metadata
|
AssemblyMetadata
|
A complete metadata record to use instead of the curated table's row. |
None
|
Returns:
| Type | Description |
|---|---|
VerifiedAssembly
|
The digest computed, what it was held to and what supplied that, and — for a
chimera — what comparing the components settled. A digest that disagrees raises
rather than reporting :attr: |
Raises:
| Type | Description |
|---|---|
ChecksumMismatchError
|
If a digest is expected and the file's is a different one. |
RegistrationError
|
If the assembly's directory holds a build that cannot be trusted as finished — including one whose components were registered again underneath it. |
FileNotFoundError
|
If there is no file to read — nothing registered for |
Examples:
prepare_fasta ¶
prepare_fasta(
fasta_path: str | Path,
*,
twobit_path: str | Path | None = None,
sizes_path: str | Path | None = None,
overwrite: bool = False,
) -> GenomeFiles
Index a FASTA, convert it to 2bit, and write its chrom.sizes.
Convenience wrapper that runs :func:faidx, :func:fasta_to_2bit, and
:func:twobit_to_chrom_sizes in sequence and collects their outputs. Each
step is cached: an output already present and newer than its input is reused
rather than regenerated, so re-running is cheap (see overwrite).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fasta_path
|
str or Path
|
FASTA to process. |
required |
twobit_path
|
str or Path
|
Override for the |
None
|
sizes_path
|
str or Path
|
Override for the |
None
|
overwrite
|
bool
|
Force every step to rerun even when its output looks fresh. |
False
|
Returns:
| Type | Description |
|---|---|
GenomeFiles
|
Paths to the source FASTA and the three generated files. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
RuntimeError
|
If any of the underlying native tools fail. |
Examples:
read_chrom_sizes ¶
Read a chrom.sizes file into a pandas Series of lengths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sizes_path
|
str or Path
|
Two-column |
required |
Returns:
| Type | Description |
|---|---|
Series
|
Integer lengths indexed by chromosome name (index name |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
Examples:
assembly_metadata ¶
assembly_metadata(
assembly: str,
*,
table: Sequence[AssemblyMetadata] | None = None,
) -> AssemblyMetadata
Return what is known about assembly — the table's row, or an unknown record.
The total accessor, and the one to reach for when the question is what are this
assembly's identifiers: there is always a record, so a caller reads a field rather
than a record and then a field. An assembly the table does not list has every
identifier None and its own name, which is what an unlisted assembly knows about
itself.
Deliberately not the same function as :func:lookup_assembly, which answers the
other question — does the curated table list this name? — and keeps its None
for it. That answer is what separates a chimera's derived name from a free-form local
key on a machine holding neither, so it cannot be made total
without reading every name as listed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The name to look up, matched as :func: |
required |
table
|
sequence of AssemblyMetadata
|
The rows to look in, as :func: |
None
|
Returns:
| Type | Description |
|---|---|
AssemblyMetadata
|
The table's row for |
Examples:
assembly_table
cached
¶
Return every assembly the shipped table lists, in table order.
What the lab officially supports — a pinned source and a pinned checksum each — and
what every lookup here reads when it is handed no table= of its own. Read once
and cached; the records are frozen, so the tuple is safe to hold on to.
Returns:
| Type | Description |
|---|---|
tuple of AssemblyMetadata
|
One record per row of |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
If the shipped file is empty, its header is not :data: |
Examples:
format_table_row ¶
Render one metadata row as a tab-separated line, in table-column order.
Blank means unknown: a field that is None, or missing from row altogether,
renders as an empty cell — exactly how the shipped table spells a value nobody has
pinned yet. The result is the line to paste into data/assembly_metadata.tsv,
with no trailing newline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Field name to value, such as :func: |
required |
Returns:
| Type | Description |
|---|---|
str
|
The row's values joined by tabs, in :data: |
Examples:
lookup_assembly ¶
lookup_assembly(
assembly: str,
*,
table: Sequence[AssemblyMetadata] | None = None,
) -> AssemblyMetadata | None
Return the :class:AssemblyMetadata for a UCSC (or canonical) assembly name, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The name to look up, matched against each record's |
required |
table
|
sequence of AssemblyMetadata
|
The rows to look in; the shipped table (:func: |
None
|
Returns:
| Type | Description |
|---|---|
AssemblyMetadata or None
|
The row for |
Examples:
>>> lookup_assembly("hg38").ncbi_name
'GRCh38'
>>> lookup_assembly("hg38").source_url
'https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz'
>>> lookup_assembly("no_such_assembly") is None
True
>>> mine = AssemblyMetadata.unknown("my_ref")
>>> lookup_assembly("my_ref", table=[mine]) == mine
True
assembly_data_dir ¶
Return the directory holding all reference files for assembly.
Every file tied to a reference assembly (FASTA, indexes, annotations, …)
lives under <liulab_data>/genome/<assembly>/ so they stay co-located.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
Assembly name, e.g. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples:
assembly_repair_command ¶
Return the command that registers assembly again from scratch.
One spelling, wherever it is quoted: a broken Assembly dir names it, and so does a
Merged annotation whose only repair is rebuilding the chimera that wrote it. A
seeded assembly carries its own source into it — genome assembly register tiny --force
would fetch from the golden path, which is not where such an assembly came from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly to register again. |
required |
source
|
str or Path
|
Where its FASTA came from, for an assembly that was seeded rather than fetched. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A command that runs as it stands. |
Examples:
is_prepared ¶
Return whether assembly is registered under the shared data root, by its record alone.
By name and not by path, exactly as a chimera's recorded components are found again: a component is addressed by the key it was registered under. A directory holding files but no record is not prepared — nothing vouches for it.
The rule itself is :attr:~genome.assembly.registration.AssemblyDir.is_registered,
asked of the directory the layout puts this name in. This is that question by name,
which is what a caller holding a name rather than a directory asks, and what
:func:~genome.assembly.status.assembly_status reports for every name at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
assembly
|
str
|
The assembly name to look for. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether a completion record is there. |
Examples:
assembly_status ¶
Report what the curated table offers against what is prepared on this machine.
What genome assembly list runs, and the whole of it: the command formats this and
holds no rule of its own. Unlike :func:~genome.annotation.registry.annotation_status
it takes no assembly to scope it — its scope is every assembly, offered or here.
Nothing is fetched, prepared, built or created to answer. A tree that does not exist is a fresh install rather than a failure, and reports the table's rows with nothing registered.
What in the tree counts as an assembly is a rule rather than a guess, because enumerating a directory means treating whatever is in it as the answer: the layout files one directory per assembly directly under the root and names it for the assembly, so an entry counts when it is a directory whose name does not begin with a dot. A file there belongs to no assembly, and no assembly is registered under a hidden name. Whether a directory that counts is registered is then the record's business and not the name's.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
str or Path
|
Override which directory the assembly tree is read from. Defaults to
:func: |
None
|
Returns:
| Type | Description |
|---|---|
AssemblyStatus
|
The tree's root, and one :class: |
Examples:
genome.homology ¶
Which genes in another species a gene is homologous to, on Ensembl Compara's trees.
The Orthology context, and a peer of :mod:genome.tf rather than a part of it. A
Homology set is anchored to a species pair and a pinned Release — never to an
Assembly or an Annotation — is downloaded once into the Data dir as a sibling
of the assembly tree, and answers with no Genome open, the shape motif/ already
established. It reads bulk files fetched once and never a remote API, so a pipeline built
on it does not fail intermittently the way one built on BioMart does.
Orthology is served and never consumed. This answers a user's cross-species question and nothing else: no TF gene table, no Cofactor table, no list this package publishes is derived through homology, and no answer is ever silently species-mapped. A species with no census still has none — what changed is that a user can now cross the line themselves, deliberately, with the publisher's Homology type in hand.
Attribution. Ensembl Compara, Herrero J et al., Database (Oxford) 2016:bav096
(PMID 26896847), from https://ftp.ensembl.org/pub/. Every link, every Homology type
and every confidence field is the publisher's; this package computes none of them and
ranks nothing. :meth:~genome.homology.metadata.HomologyMetadata.attribution renders the line to
print, and src/genome/data/homology/ATTRIBUTION.md carries the same facts beside the
provenance table.
Examples:
>>> from genome.homology import homology_table, homology_data_dir
>>> {row.publisher for row in homology_table()}
{'Ensembl Compara'}
>>> import os
>>> os.environ["LIULAB_DATA"] = "/scratch/liulab"
>>> homology_data_dir()
PosixPath('/scratch/liulab/homology')
>>> del os.environ["LIULAB_DATA"]
ComparaFileError ¶
Bases: ValueError
A file read as Compara's is not one, or a prepared slice is not what was recorded.
One class for three states because they mean the same thing to a caller — what is on
disk is not what it should be — and the repair is the same: delete the set's directory
and construct it again. The third is a cell that cannot be read as the column's own
type, which is a different fact from :data:NULL_CELL and never folded into it: the
publisher recorded no score and this package could not read the score it recorded
would otherwise both arrive as None in a column a caller filters on. The message
names the file and the repair, and for a bad cell the column and the value too.
Examples:
ComparaPartitionError ¶
Bases: RuntimeError
The file recorded as holding a species pair holds none of its rows.
The trap this module exists for. Compara's per-species dumps are a de-duplicated partition at the pair level, so a pair lives in exactly one of its two files and the assignment is arbitrary and unstable across releases. A slice that comes back empty therefore means the partition moved, not that the two species share no homologs — and a pair is never partially present, which is what makes zero trustworthy. The message names the other file, which is where the pair now is.
A :class:RuntimeError rather than a :class:LookupError: nothing the caller asked
for is missing, the shipped provenance row is out of date.
Examples:
HomologySet ¶
HomologySet(
species: str,
other_species: str,
release: str = DEFAULT_RELEASE,
*,
cache_dir: str | Path | None = None,
progressbar: bool = True,
)
One Ensembl Compara Release, sliced to one species pair and read into an index.
Constructing one prepares it, as opening a :class:~genome.assembly.genome.Genome does: the
per-species dump that holds the pair is fetched into :func:homology_data_dir on the
first construction — verified against the publisher's own md5 as it arrives — sliced to
the pair, and recorded with a Completion marker; every construction after re-reads
what is there and fetches nothing. The lab's CPU cluster compute nodes have no
internet, so the first construction of a set must happen on a login node, exactly as
:class:~genome.tf.motif.jaspar.JasparDatabase already documents.
The pair is unordered as far as the download goes and ordered as far as the question
goes: HomologySet("Homo sapiens", "Mus musculus") and its reverse read one prepared
file, and each answers about the species it was named with first.
Nothing here is computed. Every field of every Homology link is a cell of the publisher's own file, the Homology type most of all: it is Compara's tree-derived label and is never recomputed, not after a filter and not after resolution into an Annotation. No quality score, ranking or "best ortholog" of this package's own exists, and no table this package publishes is derived through homology.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species whose genes are asked about, in either spelling. |
required |
other_species
|
str
|
The species whose homologous genes come back. |
required |
release
|
str
|
The Compara Release, one of those the shipped table pins. |
``"116"``
|
cache_dir
|
str or Path
|
The directory to prepare in, overriding the one :func: |
None
|
progressbar
|
bool
|
Show the download's progress bar. Nothing is drawn when the set is already there. |
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species asked about, as the shipped table spells it. |
other_species |
str
|
The species answered with, likewise. |
release |
str
|
The Release this is. |
path |
Path
|
The stored slice these links were read from — a plain gzipped TSV carrying the publisher's own header and rows. |
source_url |
str
|
Where those rows came from: the per-species dump that holds this pair. |
provenance |
HomologyMetadata
|
The shipped row behind all of the above.
:meth: |
null_quality_scores |
tuple of str
|
Which of :data: |
Raises:
| Type | Description |
|---|---|
UnknownHomologySpeciesError
|
If either species is not one this package prepares. |
NoHomologyPairError
|
If the pair is not pinned in that release. |
ValueError
|
If the release is not pinned, or both species are the same one. |
ComparaPartitionError
|
If the recorded file holds none of the pair's rows — the partition moved. |
ComparaFileError
|
If the fetched dump is not Compara's, if a prepared slice disagrees with its record, or if one of its quality cells is neither a number nor the publisher's own null. |
Examples:
>>> from genome.homology import HomologySet
>>> worms = HomologySet("Homo sapiens", "Caenorhabditis elegans")
>>> len(worms)
23982
>>> worms.null_quality_scores
('goc_score', 'wga_coverage')
>>> worms.homologs(["ENSG00000152670"]).homolog_gene_id_stems
['WBGene00001598', 'WBGene00001599', 'WBGene00001600']
homologs ¶
Return the other species' genes homologous to each Gene id stem asked about.
The one question a Homology set answers. Every stem that named at least one
link maps to all of them, in the order the stems were asked about, and no value is
ever empty — a stem this set names no homolog for is in
:attr:~HomologyAnswer.unresolved instead, so what your list holds and this
release does not is visible rather than dropped.
Orthologs are the default and paralogs come back only on request, so the common question stays the easy one. A Paralogy link is kept in the set and marked by its own Homology type rather than excluded, which is what keeps not an ortholog distinguishable from absent — the same stance taken for a Cross-species link. Release 116 publishes none for these pairs; see this module's own documentation for the count.
Whatever a filter removed is counted in :attr:~HomologyAnswer.dropped_partners
rather than silently gone, and the Homology type on a link that survived is
untouched by it: a view can look one-to-one and still be labelled
ortholog_one2many.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stems
|
iterable of str
|
The Gene id stems to ask about, in the order they should come back. Repeats are asked once. Compara writes its gene ids bare, so a versioned id is refused rather than answered emptily. |
required |
paralogs
|
bool
|
Return every link the publisher wrote for these genes, rather than only the ones its label calls a speciation event. |
False
|
Returns:
| Type | Description |
|---|---|
HomologyAnswer
|
The stems that named homologs, mapped to every link each names, and the stems that named none. |
Raises:
| Type | Description |
|---|---|
VersionedGeneIdError
|
If a stem carries a version; the message names the stem to pass. |
Examples:
HomologySetNotDownloadedError ¶
Bases: PreparedSetNotDownloadedError
The set is not prepared here, and the publisher's dump could not be fetched.
The Orthology context's own spelling of what every Prepared set raises here, so the
message names this pair and quotes :func:homology_prepare_command.
:class:genome.xref.xref.XrefSetNotDownloadedError is the Xref context's; they are two
classes because they are two contexts, and what they share — that fetching is the one
step that needs the network, and that the lab's compute nodes have none — is said once
on the class they both derive from.
Examples:
NoHomologyPairError ¶
UnknownHomologySpeciesError ¶
Bases: LookupError
A species no Homology set is prepared for, so the question cannot be answered.
A :class:LookupError and never an empty answer: nobody pinned this species must
never read as this species has no homologs. The message names the species that do.
Examples:
VersionedGeneIdError ¶
Bases: ValueError
A versioned gene id was passed where a Gene id stem is the key.
Compara keys its dumps by stem, so ENSG00000141510.18 matches nothing and would
ride back in unresolved looking exactly like a gene Compara never placed in a tree.
Joining a versioned id to a bare one returning zero matches in silence is the most
error-prone detail in this landscape, so it is refused by name instead. The message
names the stem to pass.
:class:genome.tf.link.VersionedGeneIdError refuses the same thing for the same
reason in the TF context; they are two classes because they are two contexts, and
nothing is expected to catch both.
Examples:
HomologyMetadata
dataclass
¶
HomologyMetadata(
release: str,
species: str,
other_species: str,
holding_species: str,
publisher: str,
pubmed_id: int,
source_url: str,
md5: str,
)
Where one species pair's homologies are published (one row of the table).
The single declaration of what a Homology set's provenance consists of: the table
is parsed through these fields, column by column, exactly as
:class:~genome.assembly.metadata.AssemblyMetadata parses its own. Every column is required —
a set nobody can cite is one this package may not point anyone at, and a set with no
checksum is one a truncated fetch would answer from.
The pair is unordered: a row is looked up by its two species in either order, since which of them a caller asks about is a property of the question and not of the file.
Attributes:
| Name | Type | Description |
|---|---|---|
release |
str
|
The Ensembl Compara Release, as the publisher numbers it — |
species |
str
|
One species of the pair, as the assembly metadata table spells it. |
other_species |
str
|
The other, likewise. |
holding_species |
str
|
Whose per-species file actually holds this pair's rows in this release — measured by counting, never assumed, and re-checked every time a set is prepared. |
publisher |
str
|
Who published it, and who is to be cited. |
pubmed_id |
int
|
PubMed id of the paper to cite. |
source_url |
str
|
The published file to fetch: the holding species' own per-species dump. |
md5 |
str
|
The publisher's own md5 for those bytes, read from the |
Examples:
>>> row = homology_metadata("Caenorhabditis elegans", "Homo sapiens", "116")
>>> row.holding_species, row.md5[:8]
('Homo sapiens', '59857f48')
>>> row.pair
('Caenorhabditis elegans', 'Homo sapiens')
pair
property
¶
The two species, sorted — the key a pair is looked up and filed under.
other
property
¶
The species of the pair whose file does not hold it, in this release.
What a :class:~genome.homology.compara.ComparaPartitionError names when the
recorded file comes back empty: the partition moved, and this is where the pair
went.
from_row
classmethod
¶
Read one row of the provenance table into a record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to str
|
One row, keyed by column name. |
required |
origin
|
str
|
Where the row came from, named in any error. |
required |
Returns:
| Type | Description |
|---|---|
HomologyMetadata
|
The record. |
Raises:
| Type | Description |
|---|---|
HomologyMetadataError
|
If a required cell is blank or a numeric one is not a number. |
Examples:
>>> row = {
... "release": "116",
... "species": "Homo sapiens",
... "other_species": "Mus musculus",
... "holding_species": "Mus musculus",
... "publisher": "Ensembl Compara",
... "pubmed_id": "26896847",
... "source_url": "https://example.invalid/x.tsv.gz",
... "md5": "0" * 32,
... }
>>> HomologyMetadata.from_row(row, origin="test").pubmed_id
26896847
attribution ¶
Return the one line to print beside anything this set answered.
What a caller owes the publisher, rendered once here so the CLI, a notebook and an error message all say it the same way.
Returns:
| Type | Description |
|---|---|
str
|
Publisher, release, PubMed id and source URL. |
Examples:
HomologyMetadataError ¶
Bases: ShippedTableError
A row of the shipped provenance table cannot be read as a record.
A defect in this package rather than anything a caller did, so the message names the file, the row and the column that refused, and the repair is to fix that cell.
Examples:
resolve_homologs ¶
resolve_homologs(
answer: HomologyAnswer,
registry: AnnotationRegistry,
name: str | None = None,
) -> ResolvedHomologs
Return answer's homologous genes in one registered annotation's own gene ids.
The other species' Gene id stems are resolved in one call against registry,
which is the annotation hop this package already had; every link whose partner that
annotation carries a gene for is kept, with its Homology type untouched, and every
partner it carries none for is reported in :attr:~ResolvedHomologs.dropped_partners
rather than dropped in silence — added to whatever the answer had already dropped,
since a Dropped partner is one the answer no longer names whichever step removed
it. Which quality columns the set holds nothing in is a fact about the set rather than
about the crossing, and rides through unchanged.
The registry must annotate the answer's other species. Nothing here checks that —
an assembly's species is the assembly's own metadata and a registry does not carry
one — so passing a mouse answer a worm registry is a question about the wrong genome
and will simply resolve nothing. Reach the registry from the assembly whose species is
:attr:~HomologyAnswer.other_species.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
answer
|
HomologyAnswer
|
What :meth: |
required |
registry
|
AnnotationRegistry
|
The registry of the Assembly whose annotation the ids should be in. |
required |
name
|
str
|
The Registered name to resolve against. Omitted, that assembly's Default annotation answers. |
None
|
Returns:
| Type | Description |
|---|---|
ResolvedHomologs
|
The links whose partners this annotation spells, the gene ids it spells them with, the asked stems left naming nothing, every partner dropped along the way, and the quality columns the set behind it holds nothing in. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
AnnotationNotRegisteredError
|
If nothing of that name is registered there. |
NoGeneFeaturesError
|
If its database holds no gene at all. |
Examples:
>>> from genome.homology import HomologySet, resolve_homologs
>>> from genome.annotation import AnnotationRegistry
>>> answer = HomologySet("Homo sapiens", "Mus musculus").homologs(
... ["ENSG00000141510"]
... )
>>> crossed = resolve_homologs(
... answer, AnnotationRegistry.locate("mm39"), "gencode_vM39"
... )
>>> crossed.homolog_gene_ids
['ENSMUSG00000059552.13']
check_pair ¶
Return the shipped provenance row for one species pair and Release.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
One species of the pair, in either spelling. |
required |
other_species
|
str
|
The other. |
required |
release
|
str
|
The Compara Release. |
required |
Returns:
| Type | Description |
|---|---|
HomologyMetadata
|
The row, with the species as the shipped table spells them. |
Raises:
| Type | Description |
|---|---|
UnknownHomologySpeciesError
|
If either species is not one this package prepares. |
ValueError
|
If the release is not pinned, or if the two species are the same one — a Homology set relates two species, and a gene's paralogs within one species are a different question this does not answer. |
NoHomologyPairError
|
If both species are prepared but not together in that release. |
Examples:
check_release ¶
check_species ¶
Return species as the shipped table spells it, else say which it prepares.
Either spelling is accepted — the assembly metadata table's "Homo sapiens" or the
slug "homo_sapiens" — and the table's own spelling comes back, so an answer names
a species one way however it was asked for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
A species name, in either spelling. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The species as the shipped provenance table spells it. |
Raises:
| Type | Description |
|---|---|
UnknownHomologySpeciesError
|
If no shipped row names that species. |
Examples:
check_stem ¶
compara_url ¶
Return the URL of one Release's homology dump for one species.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species whose per-species dump is wanted, in any spelling. |
required |
release
|
str
|
The Compara Release. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The published file's URL. |
Notes
This builds a message, never a pin. The shipped provenance table's own
source_url is the authority for a set that is actually fetched; this is what names
the other file of a pair when the partition has moved, where nothing is pinned to
name. It is not a template a release can be added through: release 113 ships these
dumps uncompressed, so its file has no .gz, and only releases 90 and 116
publish an MD5SUM at all — 91 to 112 publish no checksum of any kind. A new release
is a measured row, not a formatted string.
Ensembl's genome name is the species slug for each of the three species prepared here. It is not for every genome Ensembl carries — several are named for a subspecies or a collection — so a fourth species would bring its own name rather than this deriving one.
Examples:
homology_data_dir ¶
Return the directory holding Homology sets, which belong to no Assembly.
The Orthology context's own root under the Data dir, declared here because this is where its Prepared set is fetched into and read from.
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples:
homology_prepare_command ¶
Return the call that prepares one Homology set, for an error message to quote.
One spelling of it, so a renamed entry point is renamed once. Quoted by the error a caller repairs by fetching the set on a machine with internet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
One species of the pair, as the shipped table spells it. |
required |
other_species
|
str
|
The other. |
required |
release
|
str
|
The pinned Release. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A shell command, unquoted and unfenced — the caller decides how to set it. |
Examples:
pair_name ¶
Return the directory name one species pair is filed under, order-independent.
The two slugs, sorted and joined with a hyphen, so the pair asked for either way round reaches one directory and one download.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
One species of the pair. |
required |
other_species
|
str
|
The other. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The pair's directory name. |
Examples:
set_dir ¶
Return the directory one pair's set is prepared in, under root.
<root>/ensembl_compara/<release>/<pair>/: one directory per set, because each
carries a Completion marker of its own, and releases sit side by side so holding
two is not a re-download.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Path
|
The homology root, which is :func: |
required |
row
|
HomologyMetadata
|
The provenance row for the pair. |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The set's own directory. Nothing is created by asking. |
Examples:
slice_filename ¶
Return the name one pair's stored slice is cached under.
Compara's own naming with the pair in place of the member type, so a directory listing says which release and which two species a file is without opening it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
HomologyMetadata
|
The provenance row for the pair. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The local file name. |
Examples:
source_filename ¶
Return the name the publisher's own dump is downloaded under, inside the work area.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
HomologyMetadata
|
The provenance row for the pair. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The download's file name, carrying the release and the species whose dump it is. |
Notes
Which species that is, is the pair's holding species and not either of the two the caller named — the partition decides it, and the shipped row records which file was counted to hold the pair.
Examples:
homology_metadata ¶
Return the table's row for one species pair and Release, or None.
The pair is unordered — ("Homo sapiens", "Mus musculus") and its reverse find the
same row — because which species a caller asks about is a property of the question
and not of the published file.
None is the raw absence, and this is the one place it is how absence is said:
everything above turns it into an error naming the pairs the table does carry, so
nobody pinned this pair can never be read as these species share no homologs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
One species of the pair, as the assembly metadata table spells it. |
required |
other_species
|
str
|
The other. |
required |
release
|
str
|
The Compara Release. |
required |
Returns:
| Type | Description |
|---|---|
HomologyMetadata or None
|
The row, or |
Examples:
homology_releases
cached
¶
homology_species
cached
¶
Return every species the shipped table names, sorted, as the metadata spells them.
The species a Homology set can be asked about, read off the table rather than kept as a second list in code — so adding a species is adding rows.
Returns:
| Type | Description |
|---|---|
tuple of str
|
The species names, sorted. |
Examples:
homology_table
cached
¶
Return every row of the shipped provenance table, in table order.
Which species pairs this package can prepare a Homology set for, from which release and from whose file. Read once and cached; the records are frozen, so the tuple is safe to hold on to.
Returns:
| Type | Description |
|---|---|
tuple of HomologyMetadata
|
One record per row. |
Raises:
| Type | Description |
|---|---|
HomologyMetadataError
|
If a row cannot be read; the message names the column. |
Examples:
read_metadata ¶
Read the provenance table from text, holding it to the columns it declares.
Separate from the resource it came out of, so every way the table can be wrong is reachable without writing a broken one into the package.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The whole table, tab separated, header first. |
required |
origin
|
str
|
Where the text came from, named in every message. |
required |
Returns:
| Type | Description |
|---|---|
tuple of HomologyMetadata
|
One record per row, in table order. |
Raises:
| Type | Description |
|---|---|
HomologyMetadataError
|
If the file is empty, the header is not :data: |
Examples:
genome.store ¶
What belongs to no context and is reached by all of them.
Four modules that know about bytes, directories and hashes, and nothing about assemblies, annotations, motifs or genes. Each is here because every context needs it and none owns it:
- :mod:
~genome.store.data_dirreads$LIULAB_DATA— the Data dir the whole package files into. Which roots sit under it is each context's own to declare. - :mod:
~genome.store.fetchis the package's one fetch step, and imports nothing else from this package at all. - :mod:
~genome.store.completionis the Completion marker: the record a finished build writes and the working area it uses until it does. - :mod:
~genome.store.checksumdigests a file, and refuses one that disagrees with what was expected of it. - :mod:
~genome.store.preparedis the pipeline the three Prepared sets share, built out of the four above and out of nothing else.
Nothing here imports from :mod:genome.assembly or :mod:genome.annotation, which
is what makes this a place both of them can reach without reaching each other.
This file re-exports no callable on purpose, and the fetch step is why. Every caller
inside and outside the package holds a module — from genome.store import fetch,
from genome.store.completion import CompletionRecord — and the fetch step's one patch
point depends on that: a callable re-exported here is a second reference that
monkeypatch.setattr on the module would never reach, which is exactly the bug the
suite's offline guard exists to prevent.
The exception classes below are the one exemption, because nothing patches one. They
are re-exported so a caller can name in an except what this package hands them — a
Assembly dir that disagrees with its record, a Prepared set nothing has
prepared yet — rather than importing from a module the API reference declares free to move. The
exemption is theirs alone: a function or a non-exception class added to __all__
re-opens the hole the paragraph above closes.
Examples:
>>> from genome.store import RegistrationMismatchError
>>> issubclass(RegistrationMismatchError, RuntimeError)
True
ChecksumMismatchError ¶
Bases: ValueError
A file's contents disagree with the checksum that was expected of them.
Raised where a digest computed from bytes on disk is compared against a recorded one — the sha256 an assembly's metadata row pins, say. The message names all three things a caller needs to act: which file, what was expected, what was actually there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path
|
The file that was hashed. |
required |
expected
|
str
|
The digest that was expected of it, as a hex string. |
required |
actual
|
str
|
The digest actually computed from it. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
path |
Path
|
The file that was hashed. |
expected |
str
|
The expected digest. |
actual |
str
|
The computed digest. |
Examples:
>>> from pathlib import Path
>>> raise ChecksumMismatchError(Path("hg38.fa"), "1a2b3c", "9f8e7d")
Traceback (most recent call last):
genome.store.checksum.ChecksumMismatchError: sha256 mismatch for hg38.fa: expected 1a2b3c, got 9f8e7d. ...
RegistrationError ¶
Bases: RuntimeError
A directory holds a build that cannot be trusted as finished.
Raised on its own for a record that contradicts itself — one claiming a shape only this package writes, in a form it never would have — and subclassed for the two states a caller can tell apart by looking at the directory. Every one of them names the command that repairs it, because a caller who cannot act on the message is left guessing.
Examples:
RegistrationMismatchError ¶
Bases: RegistrationError
A record disagrees with what is on disk, so something changed behind our back.
Raised rather than rebuilt: a file that was deleted or truncated after registration is a fact worth surfacing, and guessing which of the two is right risks silently wrong results.
Examples:
>>> from genome.store import RegistrationError, RegistrationMismatchError
>>> try:
... raise RegistrationMismatchError("sacCer3.2bit: recorded 3039745, found 0")
... except RegistrationError as broken: # the parent catches this and the unfinished state
... print(broken)
sacCer3.2bit: recorded 3039745, found 0
UnfinishedRegistrationError ¶
Bases: RegistrationError
A directory holds files but no record, so a build was interrupted part-way.
Raised rather than silently resumed: the files present may be a complete build whose record was never written, or the wreckage of one killed half-way, and nothing on disk distinguishes them. An absent or empty directory is not this — that is a fresh registration and proceeds normally.
Examples:
PreparedChecksumError ¶
Bases: ValueError
What arrived is not what the set pins, so nothing is prepared from it.
A :class:ValueError: the bytes are a bad value, and a truncated download is not a
smaller release. The message names the file, both digests, which bytes the pin covers
and the command that prepares the set again.
Examples:
PreparedDecodeError ¶
Bases: ValueError
The publisher's file is not text, and what reads it here yields text.
A :class:ValueError, for the same reason the checksum error is one: the bytes are a
bad value for what is being asked of them. Every reader in this package is handed
decoded lines and all three publishers ship text, so a publisher that ships bytes is a
design question — whether a source should declare its form, and whether the reader
protocol should carry bytes — rather than something a caller repairs on disk. The
message names the file so that the wall is a wall and not a decoder's own error
surfacing from mid-stream naming nothing. What a reader stores is not this: a stored
form is digested as bytes and never decoded.
Examples:
PreparedSetNotDownloadedError ¶
Bases: RuntimeError
A Prepared set is not on disk here and its bytes could not be fetched.
A :class:RuntimeError, because nothing about the call was wrong: the bytes are simply
not here and this machine could not go and get them. Each context raises a subclass of
its own naming its own set and quoting its own prepare command — three specific
messages rather than one vague one — and the sentence that sends the caller to a login
node is :func:login_node_help, written once.
Examples:
genome.tf ¶
Transcription factors and the motifs they bind.
Two halves, kept apart because they are keyed differently. :mod:genome.tf.gene
is about the gene — which genes a published census judges transcription
factors, and the DNA-binding-domain family it classifies each one under — and so
it is keyed by gene. :mod:genome.tf.motif is about the sequence a factor
recognises — the count matrix and where it occurs in an assembly — and so it is
keyed by motif. The mapping between the two is many-to-many, which is why neither
half owns it.
Cofactors are a carve-out and not a gap. :mod:genome.tf.cofactor is a third
part and a peer of both. It is keyed by gene exactly as the census half is, and it
is a peer rather than a part of it because the question differs: that half says
whether a gene is a TF gene and of what DBD family, this one says whether
it is a Transcription cofactor and of what class. A cofactor binds no DNA
sequence-specifically and so has no motif, which is why nothing in the motif half
answers for one.
The join itself is :mod:genome.tf.link, which lives here rather than in either
half because it imports both and neither imports it. It is re-exported from this
package for that reason: it belongs to the pair, so it is addressed at the level
the pair is.
Examples:
>>> from genome.tf import motif_links
>>> motif_links("TP53", "Homo sapiens").motif_ids
('MA0106.3',)
GeneNotAssessedError ¶
Bases: LookupError
The census for that species never assessed that gene, so nothing can answer for it.
The second absence. A gene the census assessed and turned down is not this: it has a
verdict, and comes back with no links and :attr:MotifLinks.is_tf False. This is
the gene the census never looked at — a symbol it does not spell, a Gene id stem
of another species, or a typo — and answering it emptily would read as this gene has
no motifs. A gene a publisher lists as a Transcription cofactor raises the narrower
:class:TranscriptionCofactorError instead, which is this absence with a reason.
The message names the species and the census that speaks for it.
Examples:
MotifLink
dataclass
¶
MotifLink(
release: str,
species: str,
gene_id_stem: str,
symbol: str,
motif_id: str,
motif_name: str,
role: str,
partners: tuple[str, ...],
motif_tax_ids: tuple[str, ...],
is_cross_species: bool,
total_information_content: float,
rank: int,
)
One Motif link: one JASPAR profile that answers for one TF gene.
One row of a shipped table, read back and frozen. It says what the matrix is a motif of — this gene alone, or a complex and which partners — and carries everything a caller needs to re-sort the answer on: the profile's tax ids, its Cross-species link flag and its total Information content.
Attributes:
| Name | Type | Description |
|---|---|---|
release |
str
|
The JASPAR Release this link was built from. On the row rather than left to the file name, so two tables concatenate into one frame that still says which release each row came from. |
species |
str
|
The gene's species, as the assembly metadata table and its census spell it. |
gene_id_stem |
str
|
The Gene id stem the census is keyed by, and what this table is keyed by too. |
symbol |
str
|
The census's own symbol for the gene, never JASPAR's. The two differ exactly
where the shipped alias table says they do — Lambert's |
motif_id |
str
|
The Motif id, versioned: |
motif_name |
str
|
The Motif name JASPAR publishes, in JASPAR's own spelling and case, with
|
role |
str
|
:data: |
partners |
tuple of str
|
The other genes the Motif name names, upper-cased as the name spells them. Empty for a monomer, and never empty for a complex. |
motif_tax_ids |
tuple of str
|
The NCBI taxonomy ids JASPAR files this profile under, ascending. Empty for a
profile the release records no species for — |
is_cross_species |
bool
|
Whether the matrix was measured on a vertebrate other than the gene's own species. A profile with no recorded species is marked |
total_information_content |
float
|
The matrix's Information content summed over its columns, in bits. The third key of Attribution specificity, and not a quality score. |
rank |
int
|
This link's place under Attribution specificity among all of this gene's links, dense from 1 in the shipped table. It is the row's own number and not the answer's position, so filtering out Cross-species links leaves gaps in it — deliberately, since a rank that renumbered itself would no longer say how specific the attribution was. |
Examples:
>>> link = motif_links("JUN", "Homo sapiens")[6]
>>> link.motif_id, link.motif_name, link.role, link.partners
('MA0099.4', 'FOS::JUN', 'complex', ('FOS',))
>>> link.is_cross_species, link.rank
(False, 7)
is_complex
property
¶
MotifLinks
dataclass
¶
MotifLinks(
species: str,
release: str,
tax_group: str,
source: str,
gene_id_stem: str,
symbol: str,
is_tf: bool,
links: tuple[MotifLink, ...],
)
One gene's Motif links, in Attribution specificity order.
:func:motif_links' answer. It is a sequence of links — iterate it, index it, take
its length — that also says where it was cut from: the species, the Release, the
Tax group and the shipped file. That provenance is captured before any
filtering, for the reason
:class:~genome.tf.motif.jaspar.JasparDatabase hands back a plain
:class:~genome.tf.motif.motif.MotifSet when it is filtered: what comes out of a
filter is no longer the release it came from, so unless it was written down first
there is nothing left to say which release it was. Drop every Cross-species link
from mouse Ctcf and no link survives to carry the release on its row — and this
still says which release found nothing.
An empty answer is a real answer, and it is never how absence is spelled: a
species or release with no table raises :class:NoMotifLinkTableError and a gene no
census assessed raises :class:GeneNotAssessedError. What is left is a gene with no
links, and :attr:is_tf says which of the two kinds it is — a gene the census turned
down, which receives no links by design, or one it judged a transcription factor that
this Release has no profile for. 763 of human's 1,639 assessed-positive genes are
the second kind on the 2026 release.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species, as its census spells it. |
release |
str
|
The JASPAR Release the table was built from. |
tax_group |
str
|
The Tax group the table covers — :data: |
source |
str
|
The shipped file these links were read from. |
gene_id_stem |
str
|
The Gene id stem the gene was resolved to, whichever spelling was asked for. |
symbol |
str
|
The census's own symbol for that gene. |
is_tf |
bool
|
The census's own verdict. |
links |
tuple of MotifLink
|
The links, most specifically attributable first, after any filtering asked for. |
Examples:
>>> jun = motif_links("JUN", "Homo sapiens", release="2024")
>>> jun.species, jun.release, jun.gene_id_stem
('Homo sapiens', '2024', 'ENSG00000177606')
>>> jun.motif_ids[:2]
('MA0488.2', 'MA0489.3')
>>> matched = motif_links("JUN", "Homo sapiens", release="2024", cross_species=False)
>>> matched.motif_ids[:2]
('MA0488.2', 'MA1131.2')
>>> smad2 = motif_links("SMAD2", "Homo sapiens")
>>> len(smad2), smad2.is_tf
(0, False)
MotifLinkTable
dataclass
¶
MotifLinkTable(
species: str,
release: str,
tax_group: str,
source: str,
links: tuple[MotifLink, ...],
)
One shipped Motif link table: one species, one Release, every gene in it.
What one file says, read back and frozen. It is the whole join for that species and
release — the layer :meth:links_for cuts one gene's answer out of — and the thing
that knows which release and Tax group it is, which is why a
:class:MotifLinks copies that down before it filters.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species, as every row of the file names it. |
release |
str
|
The JASPAR Release, as every row of the file names it. |
tax_group |
str
|
The Tax group the file covers — :data: |
source |
str
|
Where the bytes came from. |
links |
tuple of MotifLink
|
Every link in the file, in file order: by Gene id stem, then by rank. |
Examples:
>>> table = motif_link_table("Homo sapiens", "2026")
>>> table.release, table.tax_group, len(table)
('2026', 'vertebrates', 1085)
>>> len(table.gene_id_stems)
876
>>> table.links_for("TP53").motif_ids
('MA0106.3',)
gene_id_stems
property
¶
Return every Gene id stem the table links, once each, in file order.
The genes this release has a motif for, which is a strict subset of the genes the census judged transcription factors — the rest are assessed positive and have no JASPAR profile.
Examples:
__len__ ¶
frame ¶
Return the table as a fresh :class:~pandas.DataFrame, one row per link.
Built for the caller each time it is asked for, so mutating it cannot reach the cached table behind it. The columns are the file's own twelve in file order, with the multi-value cells as tuples and the flag as a boolean.
Returns:
| Type | Description |
|---|---|
DataFrame
|
The links, indexed from zero in file order. |
Examples:
links_for ¶
Return one gene's Motif links, most specifically attributable first.
The gene is named by its Gene id stem or by the symbol its own census
publishes, and a versioned gene id is refused rather than stemmed — see
:func:motif_links, which is this method with the table looked up for you and is
what a caller normally holds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gene
|
str
|
A Gene id stem — |
required |
cross_species
|
bool
|
Whether to keep links whose profile was measured on another vertebrate. Pass
|
True
|
Returns:
| Type | Description |
|---|---|
MotifLinks
|
The links, in Attribution specificity order, carrying this table's provenance — copied down before the filter, since what a filter returns is no longer the release it came from. |
Raises:
| Type | Description |
|---|---|
GeneNotAssessedError
|
If this species' census never assessed that gene. |
TranscriptionCofactorError
|
If it never assessed that gene and a publisher lists it as a Transcription cofactor; a narrowing of the error above. |
VersionedGeneIdError
|
If |
MotifLinkTableError
|
If no census ships for this table's species, which no shipped table can be in — a link table is built from a census. |
Examples:
MotifLinkTableError ¶
Bases: ShippedTableError
A shipped Motif link table cannot be read, so it is not allowed to answer.
A packaging defect and not a caller error: these files ship inside the package and
are written by scripts/build_tf_links.py, so a header that is not the twelve
columns, a row with the wrong number of cells, a Role nothing spells that way or
two releases inside one file are faults in what was committed here. A
:class:ValueError, because a file that says something the format does not is a bad
value rather than a broken program.
The message names the file and what is wrong with it, since regenerating that file is the only thing anyone can do about it.
Examples:
NoMotifLinkTableError ¶
Bases: LookupError
No Motif link table ships for that species, Release or Tax group.
The first of the two absences, and the one a caller must never read as no motif
answers for this gene: no table was ever built for what was asked, so the question
was not answered rather than answered in the negative. A :class:LookupError, as the
curated gene lists' own pair of absences are, so a caller may catch that and still
tell this from :class:GeneNotAssessedError.
The message names the species, releases and Tax group that do ship.
Examples:
TranscriptionCofactorError ¶
Bases: GeneNotAssessedError
No census assessed that gene, and a publisher lists it as a Transcription cofactor.
The same absence with something known in its place: a cofactor acts on transcription without recognising a sequence of its own, so there is no motif to look for rather than one nobody has found yet, and the census's plain silence would read as nothing here knows this gene.
It subclasses :class:GeneNotAssessedError because that is literally true — no TF census
assessed this gene — so an except clause written before this error existed keeps
covering every gene it covered. The is-a is about the censuses and not about biology: a
cofactor is not a kind of transcription factor, and being one never suppresses the motifs
a census already reached, which is why the census is asked first.
The message names the census that did not assess the gene, the publisher that lists it as a cofactor, and that there is no motif here to look for.
Examples:
VersionedGeneIdError ¶
Bases: ValueError
A versioned gene id was passed where a Gene id stem is the key.
Not an absence — the gene is assessed and its links are here — so it is a
:class:ValueError and a caller catching :class:LookupError for a missing gene does
not swallow it. A stem may name more than one gene id in one Annotation:
ENSG00000182378.14 and ENSG00000182378.14_PAR_Y are two genes of one stem in
gencode_v50lift37, and the census reached one verdict for the stem. Answering a
versioned id would therefore answer for the stem — which names a gene the caller did
not — so it is refused, in the same spirit as
:meth:~genome.annotation.registry.AnnotationRegistry.resolve_gene_ids, which answers a stem with
every gene id it names and never picks one.
The message names the stem to pass instead.
Examples:
motif_link_table
cached
¶
motif_link_table(
species: str,
release: str = DEFAULT_RELEASE,
tax_group: str = DEFAULT_TAX_GROUP,
) -> MotifLinkTable | None
Return the Motif link table shipped for one species and Release, or None.
The raw absence, and the only place None is an acceptable way to say it: this is
the layer below the one a caller touches, and :func:motif_links above it turns the
None into an error naming what does ship, so that no table was built for this
can never be read as this gene has no motifs.
The species is slugged and then looked up among what :func:shipped_link_tables
found, rather than joined onto the resource directory, so a name shaped like a path
finds nothing instead of walking out of it. Read once per table and cached; everything
it returns is frozen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, either as the assembly metadata table spells it ( |
required |
release
|
str
|
The JASPAR Release to read the links of. The motif half's default, so a new analysis links against the same release a fresh scan loads. |
``"2026"``
|
tax_group
|
str
|
The Tax group. Only :data: |
``"vertebrates"``
|
Returns:
| Type | Description |
|---|---|
MotifLinkTable or None
|
The table, or |
Raises:
| Type | Description |
|---|---|
MotifLinkTableError
|
If a table ships and cannot be read, or names a species or release its file name does not; the message names the file. |
Examples:
motif_links ¶
motif_links(
gene: str,
species: str,
*,
release: str = DEFAULT_RELEASE,
tax_group: str = DEFAULT_TAX_GROUP,
cross_species: bool = True,
) -> MotifLinks
Return the JASPAR motifs that answer for one TF gene, most specific first.
The entry point. It reads the shipped table for that species and Release and cuts one gene's links out of it — nothing is downloaded, nothing on disk is touched, and no Assembly is involved.
What names a gene here. A Gene id stem, which is what the tables and the
censuses are keyed by, or the symbol the gene's own census publishes, in any case —
the two censuses spell one factor JUN and Jun, and each spelling is unique
within its own census. A versioned gene id is refused rather than stemmed: a stem
may name more than one gene id in one Annotation, so answering ENSG00000182378.14
would answer for a stem that also names ENSG00000182378.14_PAR_Y, and this package
never picks a gene the caller did not name (see
:meth:~genome.annotation.registry.AnnotationRegistry.resolve_gene_ids, which crosses that gap in
the other direction). Pass the stem, and the error says which one.
The species is passed and never inferred. A table is named by a species and a release, and a Gene id stem's prefix is not a claim about which species it belongs to — deriving one from the other is the guess a local assembly key exists to forbid. A caller holding an assembly has its species already, in the assembly's own metadata row.
The census is asked first, and the order is what keeps this correct. A gene the
census assessed is answered whatever else is known about it — the 151 human genes that
are both a TF gene and a Transcription cofactor, TBP and KMT2A and DNMT1 among
them, come back exactly as they always did, because a second table must never suppress
an answer the census already reached. Only then is that species' Cofactor table
asked, and a gene it lists raises :class:TranscriptionCofactorError: a cofactor
recognises no sequence of its own, so no motif to look for is a truer answer than the
census's silence. A gene neither knows raises :class:GeneNotAssessedError, as it
always has, and so does every gene of a species that ships no cofactor table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gene
|
str
|
A Gene id stem or the census's own symbol for the gene. |
required |
species
|
str
|
The species, as the assembly metadata table spells it or as its slug. |
required |
release
|
str
|
The JASPAR Release to link against. Both releases the package prepares have tables; asking for another raises and names the ones that ship. |
``"2026"``
|
tax_group
|
str
|
The Tax group. Only :data: |
``"vertebrates"``
|
cross_species
|
bool
|
Whether to keep links whose profile was measured on another vertebrate.
|
True
|
Returns:
| Type | Description |
|---|---|
MotifLinks
|
The gene's links in Attribution specificity order, carrying the release, tax group and file they were cut from. |
Raises:
| Type | Description |
|---|---|
NoMotifLinkTableError
|
If no table ships for that species, release or tax group; the message names what does. |
GeneNotAssessedError
|
If that species' census never assessed that gene. |
TranscriptionCofactorError
|
If it never assessed that gene and a publisher lists it as a Transcription
cofactor; a narrowing of the error above, so an |
VersionedGeneIdError
|
If |
Examples:
>>> ctcf = motif_links("CTCF", "Homo sapiens")
>>> ctcf.motif_ids
('MA1930.2', 'MA1929.2', 'MA0139.2')
>>> jun = motif_links("Jun", "Mus musculus")
>>> [(link.motif_id, link.is_cross_species) for link in jun][:2]
[('MA0489.3', False), ('MA0488.2', True)]
>>> motif_links("Jun", "Mus musculus", cross_species=False).motif_ids
('MA0489.3',)
>>> motif_links("T", "Homo sapiens").links[0].motif_name
'TBXT'
>>> len(motif_links("FOXM1", "Homo sapiens", release="2024"))
0
parse_motif_link_table ¶
Read one Motif link table's text into links, holding it to what a table promises.
A pure function from text to links: it opens nothing, downloads nothing and
decompresses nothing. Separate from the resource it came out of, as
:func:~genome.tf.motif.jaspar.parse_transfac is, so every way a file can be wrong is
reachable without writing a broken one into the package. The shipped tables are
gzipped and :func:motif_link_table unpacks them at the resource boundary, which is
where :func:~genome.tf.gene.tf_gene_table unpacks a census too — the seam is between
the bytes and the format, and it does not move because the bytes are compressed.
The Release and the species are read off the rows rather than passed in, and every
row must agree about both — they are on each row so that two tables concatenate into
one frame that still says where each row came from, which is a promise only if they
are uniform within a file. The Tax group is the one key no row carries, since
:data:LINK_TAX_GROUP is all that ships.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The whole table, unpacked. These are a few hundred kilobytes, so they are read whole. |
required |
source
|
str
|
Where the text came from; named in every message, since regenerating that file is the only repair. |
required |
Returns:
| Type | Description |
|---|---|
MotifLinkTable
|
The table the text spells, in file order. |
Raises:
| Type | Description |
|---|---|
MotifLinkTableError
|
If the header is not :data: |
Examples:
>>> header = "\t".join(LINK_COLUMNS)
>>> row = "2026\tHomo sapiens\tENSG00000141510\tTP53\tMA0106.3\tTP53\tmonomer\t\t9606\tno\t20.6607\t1"
>>> table = parse_motif_link_table(f"{header}\n{row}\n", source="one.tsv")
>>> table.species, table.release, table.links[0].motif_name
('Homo sapiens', '2026', 'TP53')
shipped_link_tables
cached
¶
Return every Motif link table that ships, as (species slug, release).
What can be asked about at all, and what an error names when something cannot be. The directory is enumerated and the two keys read out of each file name, so neither the species nor the releases are listed in code and adding either is dropping a file in.
Returns:
| Type | Description |
|---|---|
tuple of (str, str)
|
One pair per shipped table, sorted. Empty only if the package ships no table. |
Examples:
genome.tf.motif ¶
TF binding motifs — the matrices themselves, and finding where they occur.
MotifComparison ¶
How a set of query Motifs compares against a set of target motifs.
A thin wrapper over an :class:xarray.Dataset — the array is the answer, and this
class exists to say which of its two shapes it is in, to keep the ranking rule in one
place, and to refuse the one question a limited comparison cannot answer. Reach for
:attr:data whenever you want the array itself.
Two shapes, and every method here handles both.
- Complete, from
compare(queries): dimensions(query, target), both labelled with Motif ids, every pair scored. :attr:target_idsanswers, anddata.sel(query=..., target=...)reaches any cell. - Limited, from
compare(queries, top=n): dimensions(query, rank), where rank 0 is each query's best target. The target axis is per query — rank 0 names a different motif for each row — so the target ids ride along as atargetdata variable rather than as a coordinate, and there is no shared axis to index or to widen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Dataset
|
The labelled array, in either shape. Must carry a |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> from genome.tf.motif import Motif, MotifSet
>>> counts = np.array([[19.0, 1.0, 1.0], [1.0, 19.0, 1.0],
... [1.0, 1.0, 19.0], [1.0, 1.0, 1.0]])
>>> published = MotifSet([Motif("MA0001.1", "", np.tile(counts, 3))])
>>> comparison = published.compare(Motif("pattern_0", "", np.tile(counts, 3)))
>>> comparison
MotifComparison(queries=1, targets=1, top=None)
>>> comparison.is_ragged
False
>>> float(comparison.data["neg_log10_p"].sel(query="pattern_0", target="MA0001.1")) > 0
True
data
property
¶
The labelled array itself, indexable by Motif id.
Examples:
is_ragged
property
¶
Whether the target axis is per query, which a top limit makes it.
Examples:
top
property
¶
The limit this comparison was run with — None when every pair was scored.
query_ids
property
¶
Every query Motif id, in the order the queries were given.
target_ids
property
¶
Every target Motif id, in the order the target set held them.
Returns:
| Type | Description |
|---|---|
tuple of str
|
The shared target axis. |
Raises:
| Type | Description |
|---|---|
RaggedComparisonError
|
If this comparison was limited. A limited comparison has no shared target axis: rank 0 names a different motif for each query. |
targets_compared
property
¶
How many target motifs the comparison ran against, whichever shape it is in.
to_frame ¶
Flatten to one row per query-target pair, best first within each query.
The default is the one question most callers have — what does each of my motifs
look like — so it hands back one row per query. Ranking is by
neg_log10_p descending, ties broken by score descending and then by the
target set's own order, which is also the order tomtom's nearest-neighbour path
returns, so the two shapes agree on which target is best.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top
|
int or None
|
How many targets to keep per query, best first. |
1
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns :data: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RaggedComparisonError
|
If this comparison was limited and |
Examples:
>>> import numpy as np
>>> from genome.tf.motif import Motif, MotifSet
>>> def spelled(motif_id, bases):
... columns = [[19.0 if b == l else 1.0 for l in bases] for b in "ACGT"]
... return Motif(motif_id, "", np.array(columns))
>>> published = MotifSet([spelled("MA0001.1", "ACGTACGTA"),
... spelled("MA0002.1", "TTTTTTTTT")])
>>> published.compare(spelled("pattern_0", "ACGTACGTA")).to_frame()["target"]
0 MA0001.1
Name: target, dtype: object
RaggedComparisonError ¶
Bases: ValueError
A limited comparison was asked about targets it never scored.
Raised by :attr:MotifComparison.target_ids and by
:meth:MotifComparison.to_frame when a comparison run with a top limit is asked
for a shared target axis or for more targets per query than it kept.
A limited comparison cannot be widened without recomputing, and that is accepted
rather than a defect. Passing top sends the work down tomtom's nearest-neighbour
path, which never scores the targets that lose — so the missing cells were not
discarded, they were never computed, and no amount of rearranging the array will
produce them. Recompute with a larger top, or with none at all for the complete
query x target array.
Examples:
JasparDatabase ¶
JasparDatabase(
release: str = DEFAULT_RELEASE,
tax_group: str = DEFAULT_TAX_GROUP,
*,
cache_dir: str | Path | None = None,
progressbar: bool = True,
)
Bases: MotifSet
One JASPAR Release and Tax group, prepared on disk and read into a set.
A :class:~genome.tf.motif.motif.MotifSet that also knows which release it is —
:attr:release, :attr:tax_group, :attr:source_url and the :attr:path its bytes
are cached at — so a Hit table produced from it can say what it was scanned with
months later. Everything a motif set does, it does; and :meth:~MotifSet.filter hands
back a plain motif set rather than another database, because a filtered release is no
longer that release.
Constructing one prepares it, as opening a :class:~genome.assembly.genome.Genome does:
the file is fetched into :func:jaspar_set_dir on the first construction and recorded
with a Completion marker, and every construction after re-reads what is there and
fetches nothing. The lab's CPU cluster compute nodes have no internet, so the first
construction of a release must happen on a login node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
release
|
str
|
One of :data: |
``"2026"``
|
tax_group
|
str
|
One of :data: |
``"vertebrates"``
|
cache_dir
|
str or Path
|
The directory to prepare in, overriding the one :func: |
None
|
progressbar
|
bool
|
Show the download's progress bar. Nothing is drawn when the file is already there. |
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
release |
str
|
The Release this is. |
tax_group |
str
|
The Tax group this is. |
path |
Path
|
The cached file these motifs were read from. |
source_url |
str
|
Where those bytes came from. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the release or tax group is not one this package prepares. |
MotifSetNotDownloadedError
|
If the release is not prepared here and could not be fetched. The message names the call to make on a login node. |
TransfacError
|
If a record in the file cannot be read. |
JasparReleaseError
|
If the file holds the wrong number of motifs, two versions of one matrix, or bytes that are not the ones its Completion marker recorded. |
RegistrationError
|
If the directory holds a file with no marker, or a marker that disagrees with what is on disk — an interrupted run, which reads as unfinished rather than as present. |
Examples:
>>> from genome.tf.motif import JasparDatabase
>>> worms = JasparDatabase("2024", "nematodes")
>>> len(worms)
103
>>> worms["MA0260"].motif_name
'che-1'
>>> worms.filter(tf_class="zinc finger")
MotifSet(motifs=27)
JasparReleaseError ¶
Bases: ValueError
A file read as a Release is not the release it should be.
Not a parse failure — every record read cleanly — but the file as a whole is wrong: the wrong number of motifs, two versions of one matrix where a non-redundant release ships exactly one, or bytes that are not the ones its Completion marker recorded. All three mean the same thing to a caller, which is why they are one class: what is on disk is not what was asked for, and the repair is to prepare the release again. The message names the file and the command.
Examples:
MotifSetNotDownloadedError ¶
Bases: PreparedSetNotDownloadedError
The release is not prepared here, and JASPAR's file could not be fetched.
The Motif context's own spelling of what every Prepared set raises here, so the
message names this release and tax group and quotes :func:jaspar_prepare_command.
Before there was one, a compute node with no internet met pooch's own transport error
and was left to work out that the repair is to run this somewhere else first.
Examples:
TransfacError ¶
Bases: ValueError
A transfac record cannot be read, so no motif is made from it.
A bad file, not a bad call: the message names the record it stopped on — by its accession, or by its position when it has none — and what was wrong with it.
Examples:
MotifScanMixin ¶
Scan Regions of this Genome and get hits in the assembly's own frame.
One method, because there is one thing a genome adds to a scan: the coordinates. The
raw forms stay exactly as they were — a :class:~genome.tf.motif.motif.MotifSet
handed a string, a mapping of sequences or a FASTA still answers in the frame of what
it was given and names no assembly.
scan_regions ¶
scan_regions(
motifs: MotifSet,
regions: Region | Iterable[Region],
*,
threshold: float = DEFAULT_THRESHOLD,
background: BackgroundArg = None,
workers: int | None = DEFAULT_WORKERS,
output: str | Path | None = None,
) -> pd.DataFrame
Scan Regions of this genome and return the Hit table, in its coordinates.
Each region's bases are fetched exactly as
:meth:~genome.assembly.genome.Genome.fetch_sequence returns them — reverse-complemented
for a - region — scanned in one call, and the hits lifted into the assembly's
frame. sequence_name carries the Chromosome name as this assembly spells
it, since a region whose chromosome the assembly does not carry raises rather than
being reconciled.
The arithmetic, so it can be checked without being run. Write the region as
[S, E) on a chromosome, L = E - S, and a hit as the 0-based half-open
[s, e) the scan found in the fetched bases:
+, and.with it: the bases run along the chromosome, so localiis chromosomeS + i. The hit is[S + s, S + e)and its Strand is unchanged. An unknown strand is not promoted to+— it is that the fetch returns forward bases for it, so there is nothing to flip.-: the bases are the reverse complement of[S, E), so localiis chromosomeE - 1 - i. A hit covering locals .. e - 1therefore covers chromosomeE - e .. E - s - 1, which as a half-open interval is[E - e, E - s): the two ends swap, and the- 1s cancel exactly because the interval is half-open — in 1-based-inclusive coordinates they would not. Its strand flips too: what matched the forward strand of the fetched bases matched the reverse strand of the chromosome.
Two checks on the - case: a hit spanning the whole region, s = 0 and
e = L, comes back as [E - L, E) = [S, E); and (E - s) - (E - e) = e - s,
so the length is preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motifs
|
MotifSet
|
The motifs to scan with — a Release, a filtered set, or de novo matrices.
Those shorter than :data: |
required |
regions
|
Region or iterable of Region
|
One region or many, on any chromosomes, in any order and overlapping freely:
two regions on one chromosome are ordinary here, and each is scanned in its
own right. A locus string is not accepted, because it carries no Strand
and the strand is the whole question — write
|
required |
threshold
|
float
|
The Threshold: one per-position p-value, converted per motif against
|
1e-4
|
background
|
sequence of float or {"auto", "uniform", "derive"}
|
The Background: four frequencies over
:data: |
None
|
workers
|
int
|
How many processes to shard the scan across — see
:meth: |
1
|
output
|
None
|
Refused, and the one scan argument that is: a scan handed an output path streams to Parquet and answers with the path, and a path holds no coordinates to lift into this assembly's frame. The refusal names what to do instead. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
The Hit table — :data: |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If a region names a chromosome this assembly does not carry or falls outside
it; or if |
See Also
genome.tf.motif.motif.MotifSet.scan_sequences : the same scan, region-local and naming no assembly.
Examples:
>>> from genome import Genome, Region
>>> from genome.tf.motif import JasparDatabase
>>> sacCer3 = Genome("sacCer3")
>>> peaks = [Region("chrI", 0, 5000, "+"), Region("chrII", 100, 900, "-")]
>>> hits = sacCer3.scan_regions(JasparDatabase(), peaks)
>>> sorted(set(hits["sequence_name"]))
['chrI', 'chrII']
AmbiguousBaseIdError ¶
Bases: LookupError
A bare base id matches several Motif ids here, so it addresses none of them.
Ordinarily impossible and checked anyway: a non-redundant Release ships exactly
one version of each matrix, which is what makes MA0139 mean MA0139.2 without a
caller having to remember the version. A set holding two versions of one matrix — a
hand-built comparison of MA0139.1 against MA0139.2, or a release that is not
the non-redundant one — breaks that, and it is said rather than resolved by whichever
came first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_id
|
str
|
The bare base id, versionless. |
required |
motif_ids
|
iterable of str
|
Every Motif id sharing it, in the set's own order. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
base_id |
str
|
The base id that was asked for. |
motif_ids |
tuple of str
|
Every Motif id sharing it. |
Examples:
>>> try:
... raise AmbiguousBaseIdError("MA0139", ["MA0139.1", "MA0139.2"])
... except LookupError as error:
... print("MA0139.1" in str(error))
True
AmbiguousMotifNameError ¶
Bases: LookupError
A Motif name labels several motifs here, so it addresses none of them.
A name is a label and not a key: 66 names collide in the 2024 Release and 71 in 2026, so indexing a Motif set by one of them would have to pick one of four CTCFs and say nothing about the other three. It raises instead, naming every matching Motif id — which is what a caller addresses one by — and the call that hands back all of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Motif name that labels several motifs. |
required |
motif_ids
|
iterable of str
|
Every matching Motif id, in the set's own order. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
The name that was asked for. |
motif_ids |
tuple of str
|
Every matching Motif id. |
Examples:
>>> try:
... raise AmbiguousMotifNameError("CTCF", ["MA0139.2", "MA1929.2"])
... except LookupError as error:
... print("MA1929.2" in str(error))
True
Motif
dataclass
¶
Motif(
motif_id: str,
motif_name: str,
counts: NDArray[float64],
offset: int = 0,
tax_group: str = "",
tf_class: tuple[str, ...] = (),
tf_family: tuple[str, ...] = (),
uniprot_ids: tuple[str, ...] = (),
pubmed_ids: tuple[str, ...] = (),
data_type: str = "",
)
One motif: a Count matrix, the identity it is addressed by, and its annotation.
Frozen, and frozen all the way down — the count matrix is copied at construction and
marked read-only, so motif.counts[0, 0] = 1 raises rather than quietly turning a
motif into a different one behind its own hash.
Equality and hashing are split on purpose. == compares every field, the matrix
element by element (a numpy array cannot be compared with the tuple equality a
dataclass generates, which is why eq=False). The hash covers the identity only —
id, name, offset and shape — never the several thousand floats behind them. Equal
motifs always agree on those four, so the contract holds; and two motifs sharing a
Motif id are meant to be the same motif, so collisions are theoretical.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motif_id
|
str
|
The Motif id — JASPAR's versioned matrix accession, |
required |
motif_name
|
str
|
The Motif name — the factor name, |
required |
counts
|
array_like
|
The Count matrix, 4 x L: one row per base in :data: |
required |
offset
|
int
|
Where this motif's column zero sits in the frame of the motif with this id as the
source published it, so |
0
|
tax_group
|
str
|
The Tax group the source filed this motif under — |
``""``
|
tf_class
|
iterable of str
|
The structural classes of the factor, e.g. |
``()``
|
tf_family
|
iterable of str
|
The families within those classes, e.g. |
``()``
|
uniprot_ids
|
iterable of str
|
UniProt accessions for the factor — several for a dimer. Stored as a tuple. |
``()``
|
pubmed_ids
|
iterable of str
|
PubMed ids for the experiment behind the matrix. Stored as a tuple. |
``()``
|
data_type
|
str
|
How the matrix was measured, e.g. |
``""``
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
An empty string or an empty tuple in one of the six annotations means the source stated nothing there — a de novo matrix out of a model carries none of them, and everything on this class still works.
Four of the six are plural and two are not. tf_class, tf_family,
uniprot_ids and pubmed_ids hold a tuple, because the source publishes several
of each for a dimer and separates them with a semicolon; tax_group and
data_type hold one string each. A bare string handed to one of the plural four is
refused rather than stored letter by letter.
Examples:
>>> import numpy as np
>>> motif = Motif(
... "MA0139.2",
... "CTCF",
... np.array([[9.0, 1.0], [1.0, 1.0], [0.0, 7.0], [0.0, 1.0]]),
... tax_group="vertebrates",
... uniprot_ids=("P49711",),
... )
>>> len(motif)
2
>>> motif.consensus
DNA('AG')
>>> motif.counts.flags.writeable
False
length
property
¶
probabilities
property
¶
The counts with each column normalised to sum to 1, as a fresh 4 x L array.
Derived on every call and never stored — the counts are the single source of truth, and a stored copy is a second one waiting to disagree.
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
Examples:
information_content
property
¶
How much each position says, in bits, in [0, 2].
Measured against a uniform reference, which is what puts the ceiling at 2: a
position fixed on one base says 2 bits, one that says nothing says 0. This is the
y-axis :meth:plot draws and the quantity :meth:trim thresholds on, so the
height you see is the number you set. Clipped to [0, 2], which only ever moves
floating-point noise.
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
Examples:
consensus
property
¶
The most common base at each position, as a typed :class:~genome.seq.DNA.
A one-letter-per-position rendering of the counts, and nothing more: it is not the
motif, and a Motif hit matched the matrix rather than this string. Positions
saying nothing still get a letter, so read it beside
:attr:information_content. Ties go to the first base in :data:BASES order, and
no IUPAC ambiguity code is ever produced — this package's alphabet is ACGT.
Returns:
| Type | Description |
|---|---|
DNA
|
One upper-case base per position, |
Examples:
__post_init__ ¶
Validate the identity and the matrix, then freeze an owned copy of the counts.
log_odds ¶
log_odds(
background: Sequence[float]
| NDArray[float64]
| None = None,
pseudocount: float = 0.01,
) -> npt.NDArray[np.float64]
Score each base at each position against a background, in bits.
The background and the pseudocount are arguments and never fields, so the same
motif can be scored against two backgrounds without becoming two motifs. Each
entry is log2(p / q), where p is the pseudocounted column probability
(count + pseudocount * q) / (column_sum + pseudocount) and q is the
background frequency for that base — the same arithmetic the scan engine does,
converted from natural log to bits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
background
|
sequence of float
|
Four frequencies over :data: |
None
|
pseudocount
|
float
|
Added to each column, split by the background, so an unobserved base scores
very low rather than |
0.01
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the background is not four positive frequencies summing to 1, or the pseudocount is not above zero. |
Examples:
trim ¶
trim(
threshold: float = _DEFAULT_TRIM_THRESHOLD,
*,
max_length: int | None = None,
min_length: int = MIN_MOTIF_LENGTH,
) -> Motif
Drop uninformative flanks, keeping the id, the name and a mapping back.
Only the ends move. A position under threshold bits is dropped from the
left while every position so far has been under it, and likewise from the right —
so an uninformative spacer in the middle of a dimeric motif is kept and the motif
can never be split in two.
The result carries the same Motif id and Motif name and an offset such
that position_in_trimmed_frame + offset is the position in the full motif's
frame. Trimming a trimmed motif composes: the offsets add up, so a Motif hit
found with either is readable in the full frame.
Two bounds hold whatever the threshold says. It never returns a motif shorter than
min_length — if the flank walk went too far, the window grows back one
position at a time, always taking the more informative of the two neighbours, and
the front one when they say the same, so the offset stays as small as it can. And
a motif already shorter than min_length is returned untouched, since trimming
cannot repair it and making it shorter would only make it worse.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
threshold
|
float
|
Bits below which a flanking position is uninformative. |
0.25
|
max_length
|
int
|
The most positions to keep. When the surviving window is longer, the less
informative end is dropped one position at a time until it fits — the back
one when the two ends say the same. Must be at least |
None
|
min_length
|
int
|
The fewest positions to keep, defaulting to :data: |
7
|
Returns:
| Type | Description |
|---|---|
Motif
|
The trimmed motif, or |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> flat = np.full((4, 3), 5.0) # 0 bits per position
>>> fixed = np.repeat(np.array([[9.0], [1.0], [0.0], [0.0]]), 8, axis=1)
>>> motif = Motif("MA9999.1", "x", np.hstack([flat, fixed, flat]))
>>> len(motif)
14
>>> trimmed = motif.trim()
>>> trimmed
Motif(motif_id='MA9999.1', motif_name='x', length=8, offset=3)
>>> trimmed.trim().offset # trimming composes
3
plot ¶
Draw the sequence logo, in bits, and return the axes it was drawn on.
A new figure when ax is None, the caller's axes when one is given — which
is how a grid of motifs is built in one figure. The y-axis is
:attr:information_content, so the height drawn is the same quantity
:meth:trim thresholds on; the transform from counts to those heights is
logomaker's own, rather than a second derivation of it here. The x-axis is this
motif's own frame, 0 .. L - 1; add :attr:offset to read a trimmed motif's
positions in the full frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ax
|
Axes
|
Axes to draw into. A new figure and axes are made when omitted. |
None
|
**kwargs
|
Any
|
Passed through to |
{}
|
Returns:
| Type | Description |
|---|---|
Axes
|
The axes drawn on, whether it was made here or handed in. |
Examples:
>>> import matplotlib
>>> matplotlib.use("Agg") # no window, no display
>>> import numpy as np
>>> motif = Motif("MA9999.1", "x", np.array([[9.0, 1.0], [1.0, 1.0],
... [0.0, 7.0], [0.0, 1.0]]))
>>> axes = motif.plot()
>>> axes.get_ylabel()
'Information content (bits)'
>>> low, high = axes.get_xlim()
>>> float(high - low) # one unit per position
2.0
MotifNotFoundError ¶
Bases: LookupError
Nothing in this Motif set is addressed by that key.
The plain absence: the key is not a Motif id, not a bare base id, and not a Motif name any motif in the set carries. Not an empty answer and never an empty collection — a set that does not hold CTCF says so rather than handing back nothing.
A :class:LookupError, so it can be caught together with
:class:AmbiguousMotifNameError and :class:AmbiguousBaseIdError and still be told
apart from them: those two mean too many, and this one means none.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The key that was asked for. |
required |
size
|
int
|
How many motifs the set holds, so an empty set explains itself. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
The key that was asked for. |
Examples:
>>> import numpy as np
>>> empty = MotifSet([])
>>> try:
... empty["CTCF"]
... except LookupError as error:
... print("holds no motifs" in str(error))
True
MotifSet ¶
Motifs held as one addressable group: indexed, filtered, and nothing else read.
The container the whole motif API hangs off, and it is built from any motifs — a
Release parsed off disk, a filtered part of one, or the de novo matrices a model
found, which is what gives those everything a release can do rather than a smaller
API. It reads no file and reaches no network; preparing a release does, and that is
:class:~genome.tf.motif.jaspar.JasparDatabase.
Indexing always returns exactly one motif, never a union type. set[key]
resolves a Motif id, then a bare base id, then a Motif name — in that order,
and the first that matches wins. A name labelling several motifs raises rather than
picking one of them; :meth:by_name is how all of them are had, and it hands back a
tuple whether the name labels four motifs or one.
Iterating yields :class:Motif objects rather than keys — the one place this reads
unlike a dict, and it reads like the collection of motifs it is. Order is the order
the motifs were given, everywhere: the tuple from :meth:by_name, the ids in an
ambiguity error, and what a filtered set holds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motifs
|
iterable of Motif
|
The motifs to hold, in the order they should be held. May be empty — a filter that matched nothing is a real answer. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If two motifs share a Motif id. An id is what a motif is addressed by, so a set holding two of one id could not answer for either. |
Examples:
>>> import numpy as np
>>> counts = np.array([[9.0, 1.0], [1.0, 1.0], [0.0, 7.0], [0.0, 1.0]])
>>> ctcf = Motif("MA0139.2", "CTCF", counts, tf_class=("C2H2 zinc finger factors",))
>>> prrx2 = Motif("MA0075.3", "PRRX2", counts, tf_class=("Homeo domain factors",))
>>> motifs = MotifSet([ctcf, prrx2])
>>> len(motifs)
2
>>> motifs["MA0139.2"] is motifs["MA0139"] is motifs["CTCF"]
True
>>> motifs.by_name("PRRX2")
(Motif(motif_id='MA0075.3', motif_name='PRRX2', length=2, offset=0),)
>>> motifs.filter(tf_class="zinc finger").motif_ids
('MA0139.2',)
motifs
property
¶
motif_ids
property
¶
motif_names
property
¶
Every Motif name, one per motif and parallel to :attr:motif_ids.
Duplicates are kept and never collapsed: a name labelling two motifs appears twice, because these are labels and there are as many of them as motifs.
Examples:
__contains__ ¶
Answer whether a key matches anything here, or whether a Motif is one of ours.
A key that matches several motifs is still in the set, though indexing by it raises: membership asks whether the set knows the key, and indexing asks it to name one motif.
Examples:
__getitem__ ¶
Return the one Motif key addresses — id, base id, or unique name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
A Motif id ( |
required |
Returns:
| Type | Description |
|---|---|
Motif
|
Exactly one motif, always — never a tuple and never a union type. |
Raises:
| Type | Description |
|---|---|
MotifNotFoundError
|
If nothing here is addressed by |
AmbiguousMotifNameError
|
If |
AmbiguousBaseIdError
|
If |
Examples:
by_name ¶
Return every Motif labelled name, always as a tuple.
A tuple of one where the name is unique, so a caller writes one code path for the
common case and the four-CTCFs case alike. Absence is not emptiness: a name
nothing here carries raises rather than handing back ().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The Motif name to look up, matched exactly. |
required |
Returns:
| Type | Description |
|---|---|
tuple of Motif
|
Every motif with that name, in the set's own order. Never empty. |
Raises:
| Type | Description |
|---|---|
MotifNotFoundError
|
If no motif here carries that name. |
Examples:
filter ¶
filter(
predicate: Callable[[Motif], bool] | None = None,
**annotations: str | Iterable[str],
) -> MotifSet
Return a plain :class:MotifSet of the motifs that match.
A plain motif set, never a database, even when called on one: a filtered Release is no longer that release, and calling it one would let a Hit table claim provenance it does not have. Everything a set does, the result still does.
Every condition given must hold — the predicate and each annotation keyword. With nothing given, every motif matches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
callable
|
Called with each :class: |
None
|
**annotations
|
str | Iterable[str]
|
One or more of |
{}
|
Returns:
| Type | Description |
|---|---|
MotifSet
|
The matching motifs, in this set's own order. Empty when nothing matched, which is a real answer and not an absence. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a keyword is not one of the six annotations. The message lists them. |
Examples:
>>> import numpy as np
>>> counts = np.ones((4, 9))
>>> zinc = Motif("MA0139.2", "CTCF", counts, tf_class=("C2H2 zinc finger factors",))
>>> homeo = Motif("MA0075.3", "PRRX2", counts, tf_class=("Homeo domain factors",))
>>> motifs = MotifSet([zinc, homeo])
>>> motifs.filter(tf_class="zinc finger").motif_ids
('MA0139.2',)
>>> motifs.filter(lambda motif: len(motif) == 9).motif_ids
('MA0139.2', 'MA0075.3')
>>> motifs.filter(tf_class=("zinc finger", "homeo")).motif_ids
('MA0139.2', 'MA0075.3')
scan ¶
scan(
sequence: str,
name: str = DEFAULT_SEQUENCE_NAME,
*,
threshold: float = DEFAULT_THRESHOLD,
background: BackgroundArg = None,
output: str | Path | None = None,
workers: int | None = DEFAULT_WORKERS,
) -> pd.DataFrame | Path
Scan one sequence with every motif here and return the Hit table.
The quick case — one locus, checked once. It answers with exactly the table
:meth:scan_sequences and :meth:scan_fasta answer with, down to the column
order and the dtypes, so nothing downstream branches on how the scan was called.
Both strands are scanned. Coordinates are 0-based half-open and always in the
forward frame, whichever strand matched, and Strand is + or - — never
., because a scan knows which of the two it scored. The interval covers the
bases the matrix scored, so a trimmed motif's hit is as long as the trimmed motif;
:attr:Motif.offset maps a position within the motif and not the interval.
The sequence is upper-cased, so a soft-masked one yields exactly the hits its upper-case equivalent does and there is no argument that would change that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
str
|
The bases to scan. A :class: |
required |
name
|
str
|
What the |
``"sequence"``
|
threshold
|
float
|
The Threshold: one per-position p-value, converted per motif against
|
1e-4
|
background
|
sequence of float or {"auto", "uniform", "derive"}
|
The Background: four frequencies over :data: |
None
|
output
|
str or Path
|
Where to stream the hits as Parquet instead of building a table. A scan too
large to hold goes to disk and hands back the path; there is no row-count
guard, because a genome-scale scan is the caller's decision. Read it back with
:func: |
None
|
workers
|
int
|
How many processes to shard the scan across. One by default, so importing
this package and calling a scan never starts a process unasked: under the spawn
start method a pool re-imports the caller's script, and an unguarded one would
re-execute itself. |
1
|
Returns:
| Type | Description |
|---|---|
DataFrame or Path
|
One row per Motif hit: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Motifs shorter than :data:MIN_MOTIF_LENGTH are not scanned and are named in
frame.attrs["motifs_skipped"]. A 6-mer cannot reach the default threshold at
all, and an engine asked for it anyway would fall back to that matrix's best
attainable cutoff and over-call in silence.
A Release answers with its release and tax group on the table; a set that
:meth:filter returned answers with None for both, since a filtered release is
no longer that release and must not claim to be.
Examples:
>>> import numpy as np
>>> counts = np.zeros((4, 8))
>>> for column, base in enumerate("GATTACAG"):
... counts["ACGT".index(base), column] = 100.0
>>> motifs = MotifSet([Motif("MA9999.1", "Gattacag", counts)])
>>> hits = motifs.scan("TTTTTGATTACAGTTTTT")
>>> hits[["motif_id", "sequence_name", "start", "end", "strand"]]
motif_id sequence_name start end strand
0 MA9999.1 sequence 5 13 +
>>> hits.attrs["motifs_scanned"], hits.attrs["threshold"]
(('MA9999.1',), 0.0001)
scan_sequences ¶
scan_sequences(
sequences: Mapping[str, str],
*,
threshold: float = DEFAULT_THRESHOLD,
background: BackgroundArg = None,
output: str | Path | None = None,
workers: int | None = DEFAULT_WORKERS,
) -> pd.DataFrame | Path
Scan named sequences — a peak set in one call — and return the Hit table.
The same table :meth:scan returns, with sequence_name carrying the mapping's
own keys and the rows in the mapping's own order. See :meth:scan for the
coordinate convention, the strand rule, the upper-casing and the provenance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequences
|
mapping of str to str
|
Name to bases. Scanned one at a time, so the peak cost is the longest sequence rather than all of them. |
required |
threshold
|
float
|
The Threshold, as a per-position p-value. |
1e-4
|
background
|
sequence of float or {"auto", "uniform", "derive"}
|
The Background. Automatic when omitted — see :meth: |
None
|
output
|
str or Path
|
Where to stream the hits as Parquet instead of building a table — see
:meth: |
None
|
workers
|
int
|
How many processes to shard the scan across — see :meth: |
1
|
Returns:
| Type | Description |
|---|---|
DataFrame or Path
|
The Hit table, empty of rows but not of schema when nothing matched. With
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> counts = np.zeros((4, 8))
>>> for column, base in enumerate("GATTACAG"):
... counts["ACGT".index(base), column] = 100.0
>>> motifs = MotifSet([Motif("MA9999.1", "Gattacag", counts)])
>>> peaks = {"peak1": "TTTTT" + "GATTACAG" + "TTTTT",
... "peak2": "CCCCC" + "CTGTAATC" + "CCCCC"} # the same site, flipped
>>> hits = motifs.scan_sequences(peaks)
>>> list(zip(hits["sequence_name"], hits["strand"], hits["start"], hits["end"]))
[('peak1', '+', 5, 13), ('peak2', '-', 5, 13)]
scan_fasta ¶
scan_fasta(
path: str | Path,
*,
threshold: float = DEFAULT_THRESHOLD,
background: BackgroundArg = None,
output: str | Path | None = None,
workers: int | None = DEFAULT_WORKERS,
) -> pd.DataFrame | Path
Scan every record of a FASTA and return the Hit table.
The same table :meth:scan returns. Records are read and scanned one at a time,
so the file is never held whole; plain or gzipped.
A record's name is its header up to the first whitespace, which is what STAR and chromap write into an alignment produced from the same file — so this table joins against that alignment with nobody renaming anything.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
The FASTA to scan, |
required |
threshold
|
float
|
The Threshold, as a per-position p-value. |
1e-4
|
background
|
sequence of float or {"auto", "uniform", "derive"}
|
The Background. Automatic when omitted — see :meth: |
None
|
output
|
str or Path
|
Where to stream the hits as Parquet instead of building a table — see
:meth: |
None
|
workers
|
int
|
How many processes to shard the scan across — see :meth: |
1
|
Returns:
| Type | Description |
|---|---|
DataFrame or Path
|
The Hit table, with |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ValueError
|
If the file is not FASTA, a record carries no name, |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> import numpy as np
>>> counts = np.zeros((4, 8))
>>> for column, base in enumerate("GATTACAG"):
... counts["ACGT".index(base), column] = 100.0
>>> motifs = MotifSet([Motif("MA9999.1", "Gattacag", counts)])
>>> with tempfile.TemporaryDirectory() as directory:
... fasta = Path(directory) / "peaks.fa"
... _ = fasta.write_text(">peak1 chrI:100-118 of nowhere\nTTTTTGATTACAGTTTTT\n")
... hits = motifs.scan_fasta(fasta)
>>> list(zip(hits["sequence_name"], hits["start"], hits["strand"]))
[('peak1', 5, '+')]
compare ¶
Ask what one or more motifs look like, against the motifs held here.
The motifs held here are the targets, and the argument is the queries — read
it as compare these against this release. The use case is naming: a chromBPNet
or TF-MoDISco run hands back matrices with no names on them, and
release.compare(de_novo) says which published motif each one most resembles.
The comparison is tomtom's, from memelite, handed the same 4 x L probability
matrices :attr:Motif.probabilities produces. What comes back is a labelled
array indexed by Motif id on both axes; see :class:MotifComparison for its
two shapes and :meth:MotifComparison.to_frame for the flat table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
Motif or iterable of Motif
|
One motif, several, or a whole :class: |
required |
top
|
int
|
Keep only this many targets per query, best first. It is not a convenience over the complete answer — it sends the work down tomtom's faster nearest-neighbour path, which never scores the targets that lose. The result is then ragged: its target axis is per query, and it cannot be widened without recomputing, which is accepted rather than a defect. Omit it for the complete query x target array. |
None
|
Returns:
| Type | Description |
|---|---|
MotifComparison
|
The labelled array and the methods that read it. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
A motif compared against itself aligns to itself perfectly — offset 0, the
whole length overlapping, on the + strand, and no target scores higher. It is
usually ranked first too, and the exception is worth knowing: TOMTOM's p-value
rewards a short dense alignment, so a long motif that embeds a shorter one can
rank the shorter one above itself. Both of the fixture's 31- and 33-column CTCF
matrices do exactly that with the 15-column MA0139.2 they contain. That is a
property of the statistic, not of this wrapper, and it is what a caller naming a
de novo pattern should expect to see from a family of nested matrices.
Examples:
>>> import numpy as np
>>> def spelled(motif_id, bases):
... columns = [[19.0 if b == l else 1.0 for l in bases] for b in BASES]
... return Motif(motif_id, "", np.array(columns))
>>> published = MotifSet([spelled("MA0001.1", "ACGTACGTA"),
... spelled("MA0002.1", "TTTTTTTTT")])
>>> published.compare(spelled("pattern_0", "ACGTACGTA")).to_frame()["target"]
0 MA0001.1
Name: target, dtype: object
>>> published.compare(published, top=1).is_ragged
True
FastaFormatError ¶
Bases: ValueError
A file handed to a scan is not FASTA, or holds a record with no name.
A bad file, not a bad call. A Hit table is keyed by sequence name, so a record that has none could not be joined to anything and is refused rather than given one.
Examples:
parse_transfac ¶
Read transfac text into Motifs, in the order the file spells them.
A pure function from text to motifs: it opens nothing, downloads nothing, and holds no
opinion about which Release the text came from. Records are separated by //,
the Count matrix is the tab-separated block under the PO header, and the six
annotations are the CC key:value lines.
Annotation values are separated by a semicolon and never by a comma. Commas occur
inside single values — "C3H(C),C2HC zinc-fingers like factors" is one class and
"PBM, CSA and/or DIP-chip" is one data type — so splitting on one would corrupt
roughly fifty records per release silently. An empty value means the source stated
nothing and becomes an empty tuple or an empty string, which is common and not an
error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The whole transfac file. These are under 1 MB, so they are read whole. |
required |
Returns:
| Type | Description |
|---|---|
tuple of Motif
|
One motif per record, in file order. Empty for empty text. |
Raises:
| Type | Description |
|---|---|
TransfacError
|
If a record has no accession, no count matrix, a |
Examples:
>>> record = '''AC MA0260.1
... XX
... ID che-1
... XX
... PO\tA\tC\tG\tT
... 01\t0.0\t0.0\t37.0\t0.0
... 02\t37.0\t0.0\t0.0\t0.0
... XX
... CC tax_group:nematodes
... CC tf_class:C2H2 zinc finger factors; Homeo domain factors
... XX
... //
... '''
>>> (motif,) = parse_transfac(record)
>>> motif
Motif(motif_id='MA0260.1', motif_name='che-1', length=2, offset=0)
>>> motif.tf_class
('C2H2 zinc finger factors', 'Homeo domain factors')
>>> motif.consensus
DNA('GA')
hit_count ¶
Return how many Motif hits a written Hit table holds, reading none of them.
Off the file's footer, where Parquet records the row count of every row group, so this costs the same on a genome-scale scan as on an empty one. It is what a summary of a finished scan reports: reading 550 million rows back to count them is the one thing a scan that streamed to disk exists to avoid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
A Parquet file written by :func: |
required |
Returns:
| Type | Description |
|---|---|
int
|
How many rows the file holds. Zero for a scan that found nothing, which still wrote a file. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> import numpy as np
>>> from genome.tf.motif import Motif, MotifSet
>>> counts = np.zeros((4, 8))
>>> for column, base in enumerate("GATTACAG"):
... counts["ACGT".index(base), column] = 100.0
>>> motifs = MotifSet([Motif("MA9999.1", "Gattacag", counts)])
>>> with tempfile.TemporaryDirectory() as directory:
... written = motifs.scan("TTTTTGATTACAGTTTTT", output=Path(directory) / "h.parquet")
... hit_count(written)
1
provenance_of ¶
Read what a written Hit table was scanned with, without reading a row of it.
The provenance alone — the Background, the Threshold, the Release, the
Tax group and the two motif lists, exactly what :func:read_hits puts on
frame.attrs. It comes out of the file's own key-value metadata, so a 550-million-row
scan answers as fast as an empty one; :func:read_hits is for when the rows are wanted
too, and a genome-scale scan is precisely when reading them back is fatal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
A Parquet file written by :func: |
required |
Returns:
| Type | Description |
|---|---|
dict
|
What :data: |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> from genome.tf.motif.scan import empty_hits
>>> with tempfile.TemporaryDirectory() as directory:
... written = write_hits([empty_hits()], Path(directory) / "none.parquet",
... {"threshold": 0.0001, "motifs_skipped": ("MA0261.1",)})
... provenance_of(written)
{'threshold': 0.0001, 'motifs_skipped': ('MA0261.1',)}
read_hits ¶
Read a Hit table back from Parquet, provenance and dtypes included.
The counterpart of :func:write_hits, and the only reader that restores
frame.attrs: :func:pandas.read_parquet alone gives the rows and drops what the
scan was, which is the same table with its meaning removed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str or Path
|
A Parquet file written by :func: |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
The Hit table, equal to the in-memory form of the same scan — column order,
dtypes and category order included — with
:data: |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> import numpy as np
>>> from genome.tf.motif import Motif, MotifSet
>>> counts = np.zeros((4, 8))
>>> for column, base in enumerate("GATTACAG"):
... counts["ACGT".index(base), column] = 100.0
>>> motifs = MotifSet([Motif("MA9999.1", "Gattacag", counts)])
>>> with tempfile.TemporaryDirectory() as directory:
... written = motifs.scan("TTTTTGATTACAGTTTTT", output=Path(directory) / "h.parquet")
... hits = read_hits(written)
>>> {name: str(dtype) for name, dtype in hits.dtypes.items()}["score"]
'float16'
>>> hits.attrs["threshold"]
0.0001
threshold_cache_dir ¶
Return the directory the per-motif cutoffs are cached in.
<liulab_data>/motif/thresholds/. Nothing is created by asking — the write creates
what it needs. Delete the directory to force every scan on this machine to recompute.
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples:
resolve_workers ¶
Return how many processes to run with.
A number is taken as given — a caller who says 2 gets 2, on a laptop or on a login
node. None means work it out, and working it out is
:data:SLURM_CPU_VARS, then process affinity, then the machine's cores. Never the
machine's cores alone: that is the answer that ignores a cluster allocation.
Nothing here starts a process, and nothing here is cached — an allocation is a property of the process, and reading it at the call site is what keeps it current.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workers
|
int
|
The count to use, or |
None
|
Returns:
| Type | Description |
|---|---|
int
|
At least 1. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
genome.xref ¶
Which foreign identifiers name a gene, and which genes a foreign identifier names.
The Xref context, and a peer of :mod:genome.tf rather than a part of it. An Xref set
is one species, one Xref source and one pinned Release: constructing it fetches the
publisher's file once into the Data dir, slices it to that species as a plain gzipped
TSV, and re-reads it on every construction after. It holds no coordinates, opens no
Genome and belongs to no Assembly — an identifier is a name and not a place.
Two directions and only two: :meth:~genome.xref.xref.XrefSet.to_stems toward
the hub and :meth:~genome.xref.xref.XrefSet.from_stems away from it — with
:meth:~genome.xref.xref.XrefSet.match_symbols the first of them asked about a gene
symbol, which is answered unlike an id and so has a verb of its own. Nothing here converts
one foreign Namespace directly into another, and nothing merges two publishers — a
query reads exactly one set, so two sources that disagree are two answers rather than a
contradiction to resolve.
Four sources ship, and they are not equals. :data:~genome.xref.alliance.ALLIANCE is
the Default xref source for all three species when the question is identifiers;
:data:~genome.xref.ensembl.ENSEMBL_TSV is selectable and pins its own numbered
Release, and its mapping fans out to 72 stems for one GeneID where the other's is
near-one-to-one. Which one answers is a scientific choice — read
:class:~genome.xref.xref.XrefSet's source parameter before making it.
The other two carry symbols, which the first two do not.
:data:~genome.xref.hgnc.HGNC_ARCHIVE is human's, from a pinned quarterly archive file,
and is the only source that publishes previous and alias spellings typed;
:data:~genome.xref.bgi.ALLIANCE_BGI is mouse's and worm's, and carries the current
approved symbol alone. So a symbol is matched with
:meth:~genome.xref.xref.XrefSet.match_symbols, whose answer says on every hit which kind
of spelling matched and, on the answer as a whole, which kinds that source could not match
and why.
A default is therefore per species and per question, and the question is
named where the source is filled in and nowhere else:
:meth:~genome.xref.xref.XrefSet.for_symbols is the constructor that reaches one of those
two, :meth:~genome.xref.xref.XrefSet.for_namespace the same fill-in for a caller holding
a Namespace rather than a verb, and the plain constructor reaches the identifier
default. They sit side by side on purpose — a set built for one publisher is never answered
out of another's bytes, so XrefSet("Homo sapiens") matches no symbol at all and raises
naming the source that does.
Putting an answer into your own annotation's gene ids is the call that already existed,
:meth:~genome.annotation.registry.AnnotationRegistry.resolve_gene_ids, which is why the hub is the
Gene id stem: what comes out of here goes straight in there.
Examples:
>>> from genome.xref import XrefSet, xref_table
>>> sorted({record.species for record in xref_table()})
['Caenorhabditis elegans', 'Homo sapiens', 'Mus musculus']
>>> human = XrefSet("Homo sapiens")
>>> human.to_stems(["HGNC:11998"], "hgnc").gene_id_stems
['ENSG00000141510']
AllianceFileError ¶
Bases: ValueError
The Alliance file is not the file this reader reads.
A bad file, not a bad call: a missing or re-spelled header, a row with the wrong
number of fields, or a GeneID under a prefix no species authority claims. Each
means the publisher changed the file's shape, and the message names the file and what
was wrong with it so the fix is a reader change rather than a guess.
Examples:
BgiFileError ¶
Bases: ValueError
The Alliance gene submission is not the file this reader reads.
A bad file, not a bad call: no data array, JSON that does not parse, or a gene
keyed by a prefix no species authority claims. The message names the file and what was
wrong with it, so the fix is a reader change rather than a guess.
Examples:
EnsemblTsvFileError ¶
Bases: ValueError
The Ensembl TSV is not the file this reader reads.
A bad file, not a bad call: a missing or re-spelled header, or a row with the wrong number of fields. Each means the publisher changed the file's shape, so the message names the file and what was wrong with it and the fix is a reader change rather than a guess.
Examples:
EmptyEvidenceFilterError ¶
Bases: LookupError
The filter kept no rows, so the set would answer every query with nothing.
A :class:LookupError and not a :class:ValueError: the evidence types named are ones
this release resolves nothing under, which is the same kind of miss as an unknown
Namespace, and the message answers it the same way — by naming what is there.
Examples:
EvidenceNotRecordedError ¶
Bases: LookupError
The Xref source's file grades nothing, so there is no evidence to filter on.
A :class:LookupError for the same reason
:class:~genome.xref.xref.NamespaceNotCarriedError is one: the caller named something
this set does not carry, and the message names a source that does. Raised rather than
ignored, because a filter that quietly does nothing leaves the caller believing their
answer was graded when it was not.
Examples:
HgncFileError ¶
Bases: ValueError
The HGNC archive file is not the file this reader reads.
A bad file, not a bad call: no header line, or a header that names none of the columns this reader needs. Not raised for a column this reader ignores, and not raised for a re-ordered one — reading by name is what makes both harmless, and a schema that has already gone from 52 columns to 54 will move again.
Examples:
NoXrefSetError ¶
Bases: LookupError
Nothing this package prepares answers for that species, source or release.
Raised by :func:lookup_xref and so by constructing an
:class:~genome.xref.xref.XrefSet. A :class:LookupError, because it is a name that
resolves to nothing rather than a malformed one, and it is the same shape as the
census and cofactor misses: the message names what is available, so a caller who
guessed a species reads the three that have a set instead of guessing again.
A species with no Ensembl presence is answered by this permanently rather than pending — the registered E. coli HT115 assembly has no hub to hang a Namespace off, so it has no Xref set and says so instead of being served a fudged one.
Examples:
XrefMetadata
dataclass
¶
XrefMetadata(
species: str,
ncbi_taxid: int,
source: str,
release: str,
publisher: str,
version: str,
pubmed_id: int | None,
url: str,
source_checksum: str,
default: bool = False,
symbol_default: bool = False,
)
One Xref set this package prepares (one row of the curated table).
The single declaration of what such a row consists of: the table is parsed through
these fields, in this order, and every one is required except pubmed_id — a
publisher with no paper is cited by name and URL, and a set nobody can cite is one
this package will not fetch.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species, as the assembly metadata table spells it — |
ncbi_taxid |
int
|
NCBI taxonomy id, which is how the species' rows are picked out of a publisher's multi-species file. Read from the row and never inferred from the species name. |
source |
str
|
The Xref source, lower-cased and stable — |
release |
str
|
The pinned Release, and the string a caller names this set by. |
publisher |
str
|
Who published it, and who is to be cited for it. |
version |
str
|
The publisher's own release identifier, which is often the same string as
|
pubmed_id |
int or None
|
PubMed id of the paper to cite, or |
url |
str
|
Where the publisher's file is fetched from. |
source_checksum |
str
|
The publisher's own checksum of that file, as |
default |
bool
|
Whether this is the species' Default xref source when the question is identifiers. |
symbol_default |
bool
|
Whether this is it when the question is symbols. A separate flag because the two answers are usually different rows: the source carrying a species' ids often publishes none of its symbols. |
Examples:
>>> record = lookup_xref("Caenorhabditis elegans")
>>> record.ncbi_taxid, record.default, record.symbol_default
(6239, True, False)
>>> record.url.endswith(".tsv.gz")
True
>>> lookup_xref("Caenorhabditis elegans", for_symbols=True).symbol_default
True
from_row
classmethod
¶
Build a record from one row of the curated table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
mapping of str to object
|
Column name to cell, as the shipped TSV spells one. Keys outside
:data: |
required |
Returns:
| Type | Description |
|---|---|
XrefMetadata
|
The record the row spells. |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
If a cell cannot be read as its column's type, or a column that has no unknown is blank. The message names the column. |
Examples:
>>> XrefMetadata.from_row(
... {
... "species": "Tiny beast",
... "ncbi_taxid": "1",
... "source": "somewhere",
... "release": "1.0",
... "publisher": "Someone et al. 1999",
... "version": "1.0",
... "pubmed_id": "",
... "url": "https://example.org/beast.tsv.gz",
... "source_checksum": "md5:" + "0" * 32,
... "default": "yes",
... "symbol_default": "no",
... }
... ).pubmed_id is None
True
attribution ¶
Return the one line to print beside anything this set answered.
What a caller owes the publisher, rendered once here so a notebook, the CLI and an error message all say it the same way.
Returns:
| Type | Description |
|---|---|
str
|
Publisher, release, PubMed id where there is one, and the source URL. |
Examples:
SymbolDirectionError ¶
Bases: ValueError
A symbol was asked for the way an id is, and the two directions are not the same.
Raised by :meth:~genome.xref.xref.XrefSet.to_stems when the Namespace named is
the symbol one. A :class:ValueError rather than a
:class:~genome.xref.xref.NamespaceNotCarriedError: the set does carry symbols, and
what is wrong is the verb. Answering it anyway would match approved spellings only and
silently drop every row that spells its gene the way the authority used to, which is
the measured failure — so the message names
:meth:~genome.xref.xref.XrefSet.match_symbols instead.
Examples:
NamespaceNotCarriedError ¶
Bases: LookupError
The set carries no such Namespace, and the message names the ones it does.
A :class:LookupError and not a :class:ValueError: the namespace is a name that this
release resolves nothing under, which is the same kind of miss as an unknown species.
The three species carry three different authorities, so a mouse set asked for hgnc
lands here rather than answering nothing — the failure that would otherwise look like
a gene list with no matches.
Examples:
ResolvedStems
dataclass
¶
ResolvedStems(
species: str,
source: str,
release: str,
namespace: str,
resolved: Mapping[str, tuple[str, ...]],
unresolved: tuple[str, ...],
)
The Gene id stems one Xref set says a foreign id names.
:meth:XrefSet.to_stems's answer — the hop toward the hub, defined beside the set
that builds it. Every field before :attr:resolved says what produced it,
because one publisher's assertions are not another's: NCBI and Ensembl agree on 57.6%
of human gene-level (GeneID, ENSG) pairs, so an answer that did not name its Xref
source and Release would be unreproducible a year later. A query reads exactly
one set, which is why the source is one field here rather than a column on
every row.
A foreign id naming two stems answers with both, and nothing picks one — the same
guarantee :class:~genome.annotation.stems.ResolvedGeneIds gives for a stem naming two gene ids.
What named nothing rides back in :attr:unresolved rather than shortening the
answer.
The keys are the caller's own spelling of the ids it asked about, so a versioned and an unversioned spelling of one id are two keys with identical values and the answer still zips against the caller's table row for row.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species this set is for, as the curated metadata table spells it. |
source |
str
|
The Xref source whose assertions these are. |
release |
str
|
The pinned Release of that source. |
namespace |
str
|
The Namespace the ids asked about belong to. |
resolved |
mapping of str to tuple of str
|
Every id that named at least one stem, in the order they were asked about, to the
stems it names, in ascending order. No value is ever an empty tuple — an id that
named nothing is in :attr: |
unresolved |
tuple of str
|
The ids this release names no stem for, in the order they were asked about. |
Examples:
>>> answer = ResolvedStems(
... species="Homo sapiens",
... source="alliance",
... release="8.4.0",
... namespace="entrez",
... resolved={"7157": ("ENSG00000141510",)},
... unresolved=("999999999",),
... )
>>> answer.gene_id_stems
['ENSG00000141510']
>>> answer.as_json()["source"]
'alliance'
gene_id_stems
property
¶
Every stem resolved, ask order and then stem order — a fresh list each call.
Every stem, not one per id. Flattening loses which id named which stem, and
with it the fact that an id named more than one: a reader taking the first stem of
each id would silently pick one of two genes a Namespace is ambiguous
between. :attr:resolved is what says which id a stem came from. It also loses
the ask order of the ids, since one id contributing two stems contributes two
entries here.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
ResolvedSymbols
dataclass
¶
ResolvedSymbols(
species: str,
source: str,
release: str,
case_insensitive: bool,
kinds: tuple[str, ...],
limits: str | None,
resolved: Mapping[str, tuple[SymbolMatch, ...]],
unresolved: tuple[str, ...],
)
The genes one Xref set says each gene symbol names, and how each one matched.
:meth:XrefSet.match_symbols's answer — the hop toward the hub from the one
Namespace that is not answered like an identifier. :class:ResolvedStems's shape
in every respect a caller relies on — ask order, no empty resolved value, what named
nothing riding back — with one difference: a value is a tuple of :class:SymbolMatch
rather than of stems, because ambiguity is the return type here and not an edge
case. A symbol naming several genes answers with all of them and nothing picks one,
and each says whether it matched an approved, a previous or an alias spelling so the
caller can judge the ambiguity themselves.
What the set could not have matched is on the answer too. :attr:kinds says which
kinds of spelling this Xref source publishes and :attr:limits says why the others
are missing, so this gene is not in the release and this source cannot match the way
you spelled it are distinguishable rather than both being silence.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species this set is for, as the curated metadata table spells it. |
source |
str
|
The Xref source whose assertions these are. |
release |
str
|
The pinned Release of that source. |
case_insensitive |
bool
|
Whether case was ignored. |
kinds |
tuple of str
|
The kinds of Symbol match this set could make, in
:data: |
limits |
str or None
|
Why the kinds not in :attr: |
resolved |
mapping of str to tuple of SymbolMatch
|
Every symbol that matched at least one gene, in the order they were asked about, to every match it made — approved first, then previous, then alias, and by stem within a kind. No value is ever an empty tuple. |
unresolved |
tuple of str
|
The symbols this release matched nothing for, in the order they were asked about. |
Examples:
>>> answer = ResolvedSymbols(
... species="Homo sapiens",
... source="hgnc",
... release="2026-07-07",
... case_insensitive=False,
... kinds=("approved", "previous", "alias"),
... limits=None,
... resolved={
... "ADCY3": (
... SymbolMatch("ADCY3", "ENSG00000138031", "approved"),
... SymbolMatch("ADCY3", "ENSG00000155897", "previous"),
... )
... },
... unresolved=("Brca1",),
... )
>>> answer.gene_id_stems
['ENSG00000138031', 'ENSG00000155897']
>>> answer.as_json()["resolved"]["ADCY3"][1]["kind"]
'previous'
gene_id_stems
property
¶
Every stem matched, ask order and then match order — a fresh list each call.
Every stem, not one per symbol, and it may repeat: one gene answering a symbol
on both an approved and an alias spelling contributes two matches and so two
entries. Flattening loses the two things this answer exists to carry — which
symbol named which gene, and which kind of spelling each match was on — so a
reader who takes this list has thrown away the means of judging the ambiguity.
:attr:resolved is what keeps both.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
ResolvedXrefIds
dataclass
¶
ResolvedXrefIds(
species: str,
source: str,
release: str,
namespace: str,
resolved: Mapping[str, tuple[str, ...]],
unresolved: tuple[str, ...],
)
The foreign ids one Xref set says a Gene id stem names.
:meth:XrefSet.from_stems's answer — the hop away from the hub, and
:class:ResolvedStems's mirror in every respect: the same four provenance fields, the
same ask order, the same never-empty resolved value, and the same tuple of what named
nothing. Two verbs and only two, so a caller wanting one Namespace from another
makes both calls and owns the join.
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species this set is for, as the curated metadata table spells it. |
source |
str
|
The Xref source whose assertions these are. |
release |
str
|
The pinned Release of that source. |
namespace |
str
|
The Namespace the answering ids belong to. |
resolved |
mapping of str to tuple of str
|
Every stem that named at least one id in that namespace, in the order the stems were asked about, to the ids it names, in ascending order. No value is ever an empty tuple. |
unresolved |
tuple of str
|
The stems this release gives no id in that namespace, in the order they were asked about. One bucket and not two: a stem this release never carried and a stem it carries with no id in this namespace are both this set answers nothing, and no id history is held that could tell a retired stem from an unknown one. |
Examples:
>>> answer = ResolvedXrefIds(
... species="Homo sapiens",
... source="alliance",
... release="8.4.0",
... namespace="hgnc",
... resolved={"ENSG00000141510": ("HGNC:11998",)},
... unresolved=("ENSG00000288541",),
... )
>>> answer.xref_ids
['HGNC:11998']
>>> answer.as_json()["namespace"]
'hgnc'
xref_ids
property
¶
Every foreign id resolved, ask order and then id order — a fresh list each call.
Every id, not one per stem. Flattening loses which stem named which id, so a
reader taking the first id of each stem would hand a collaborator one of two
accessions a gene genuinely has without saying it had chosen; and a stem naming two
ids contributes two entries, so the flattened list no longer runs parallel to the
stems asked about. :attr:resolved is what says which stem an id came from.
as_json ¶
Return this answer as --json serializes it.
Returns:
| Type | Description |
|---|---|
dict
|
|
SymbolMatch
dataclass
¶
One hit of a gene symbol against an Xref set, and which spelling matched it.
The kind rides on the match rather than being filtered away on the way out, because a table that spells a gene the way it was spelled five years ago is otherwise dropped without a word — the failure that would have hit 31 of EpiFactors' 801 rows.
Attributes:
| Name | Type | Description |
|---|---|---|
symbol |
str
|
The authority's own spelling that matched, which is not always the one asked
about: on the case-insensitive path |
gene_id_stem |
str
|
The Gene id stem that spelling names. |
kind |
str
|
|
Examples:
>>> match = SymbolMatch(symbol="ARNTL", gene_id_stem="ENSG00000133794", kind="previous")
>>> match.as_json()
{'symbol': 'ARNTL', 'gene_id_stem': 'ENSG00000133794', 'kind': 'previous'}
as_json ¶
Return this match as --json serializes it, in field order.
Returns:
| Type | Description |
|---|---|
dict
|
|
XrefSet ¶
XrefSet(
species: str,
source: str | None = None,
release: str | None = None,
*,
evidence: str | Iterable[str] | None = None,
cache_dir: str | Path | None = None,
progressbar: bool = True,
)
One species, one Xref source, one pinned Release, prepared on disk.
Constructing one prepares it, as opening a :class:~genome.assembly.genome.Genome does: the
publisher's file is fetched on the first construction, sliced to this species and
written under :func:xref_set_dir, and every construction after re-reads what is there
and fetches nothing. It answers with no genome open and belongs to no assembly.
It answers two questions and only two — :meth:to_stems and :meth:from_stems, with
:meth:match_symbols the first of them asked about a symbol, where the answer carries
the kind of spelling that matched — and never converts one foreign Namespace
directly into another. Gene level only: a gene, a transcript and a protein have
different keys and different sources and are three objects rather than one table with a
level column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the curated table's spelling ( |
required |
source
|
str
|
The Xref source. Omitted, the species' Default xref source answers — which is a default and not a recommendation: naming one is how the scientific choice gets made deliberately, and two publishers disagreeing are two answers rather than one merged one. This constructor fills in the identifier default, and a set that carries no
symbol keeps refusing to match one — it holds one publisher's bytes and answering
from another's is exactly what one query reading one set forbids.
:meth: The sources are not equals, and the choice is nearly half the answer. Measured
on human release 116 against NCBI's own file, Ensembl and NCBI agree on only
57.6% of the gene-level (GeneID, ENSG) pairs they assert between them. The
cause is method rather than release skew: NCBI's mapping is a sequence match at a
published overlap threshold and is all but one-to-one, while Ensembl's fans out
to 72 stems for one GeneID ( |
None
|
release
|
str
|
The pinned Release. Omitted, the newest the curated table lists. Each source
pins its own numbering and they do not correspond — |
None
|
evidence
|
str or iterable of str
|
Keep only the rows the publisher graded with one of these |
None
|
cache_dir
|
str or Path
|
The directory to prepare in, overriding :func: |
None
|
progressbar
|
bool
|
Show the download's progress bar. Nothing is drawn when the set is already there. |
True
|
Attributes:
| Name | Type | Description |
|---|---|---|
species |
str
|
The species, as the curated table spells it. |
source |
str
|
The Xref source whose assertions this carries. |
release |
str
|
The pinned Release. |
evidence |
tuple of str
|
The evidence filter this set was built under, empty for none. |
path |
Path
|
The stored slice these mappings were read from — a plain gzipped TSV. |
source_url |
str
|
Where the publisher's own file was fetched from. |
provenance |
XrefMetadata
|
The curated row this set actually resolved to, defaults filled in — who published it, which release, and the paper to cite. Read off the set rather than looked up again, so what is cited is what answered. |
namespaces |
tuple of str
|
The Namespaces this set actually carries, read off the slice rather than
declared, in :data: |
symbol_kinds |
tuple of str
|
Which kinds of Symbol match this set can make — |
symbol_limits |
str or None
|
Why the kinds not in :attr: |
Raises:
| Type | Description |
|---|---|
NoXrefSetError
|
If no set exists for that species, source or release. The message names what does. |
XrefSetNotDownloadedError
|
If the set is not on disk and could not be fetched. |
XrefTableError
|
If the publisher's file or the stored slice is not the shape it must be. |
EvidenceNotRecordedError
|
If an evidence filter is named and this source's file grades nothing. |
EmptyEvidenceFilterError
|
If an evidence filter is named and it keeps none of the release's rows. |
RegistrationMismatchError
|
If the Completion marker disagrees with what is on disk, either about the slice's size or about its checksum — both mean unfinished rather than present. |
Examples:
>>> from genome.xref import XrefSet
>>> human = XrefSet("Homo sapiens")
>>> human.namespaces
('ensembl', 'entrez', 'uniprot', 'hgnc')
>>> human.to_stems(["7157"], "entrez").resolved
{'7157': ('ENSG00000141510',)}
>>> human.from_stems(["ENSG00000141510.18"], "hgnc").resolved
{'ENSG00000141510.18': ('HGNC:11998',)}
>>> print(human.provenance.attribution())
Alliance of Genome Resources 9.0.0 (PMID 38552170) — https://download.alliancegenome.org/...
for_namespace
classmethod
¶
for_namespace(
species: str,
namespace: str,
source: str | None = None,
release: str | None = None,
*,
cache_dir: str | Path | None = None,
progressbar: bool = True,
) -> XrefSet
Return the set that answers a question about namespace when none is named.
A Default xref source is per species and per question, and this is
where a caller holding a Namespace rather than a verb names the question: the
symbol one fills in the species' symbol-carrying default, every other one fills in
its identifier default. It is :meth:for_symbols generalised to the shape a caller
who read a namespace off a flag already has, so that surface has nothing left to
decide — the choosing happens here and in no second place.
Which namespace a set actually carries is still the set's to say: this fills a source in and does not check, so a namespace the resolved set does not carry raises on the verb, naming the ones it does.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the curated table's spelling or its slug. |
required |
namespace
|
str
|
The Namespace the question is about, which is what picks the default. Case is not significant, as it is not on the verbs. |
required |
source
|
str
|
The Xref source. Omitted, the default this namespace's question implies answers. Named, it is honoured whatever the namespace is — naming one is the deliberate scientific choice and is never swapped for a flagged row. |
None
|
release
|
str
|
The pinned Release. Omitted, the newest that source has. |
None
|
cache_dir
|
str or Path
|
The directory to prepare in, as on the ordinary constructor. |
None
|
progressbar
|
bool
|
Show the download's progress bar. |
True
|
Returns:
| Type | Description |
|---|---|
XrefSet
|
The prepared set, which is an ordinary one in every respect — every verb answers on it and nothing about it remembers which question filled its source in. |
Raises:
| Type | Description |
|---|---|
NoXrefSetError
|
If no set exists for that species or release, or if the namespace is the symbol one and no row for the species is flagged to answer symbols. |
Examples:
for_symbols
classmethod
¶
for_symbols(
species: str,
source: str | None = None,
release: str | None = None,
*,
cache_dir: str | Path | None = None,
progressbar: bool = True,
) -> XrefSet
Return the set that answers symbols for a species when none is named.
The ordinary constructor with the question named, and nothing else: a Default
xref source is per species and per question, because the publisher
carrying a species' identifiers is usually not the one carrying its symbols — human
ids default to the Alliance, whose cross-reference file publishes no human symbol at
all, and HGNC's quarterly archive is what does. Mouse and worm reach a third source
again, alliance_bgi.
A source named here is honoured exactly as it is anywhere else. Naming one is
how the scientific choice gets made deliberately, so this fills in a default and
never overrides a choice: XrefSet.for_symbols(species, "alliance") is
XrefSet(species, "alliance"), symbols and all — which for human means a set that
matches none and says so.
This is :meth:for_namespace with the namespace fixed, so the two cannot answer
differently: one fill-in, named by a verb here and by a namespace there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the curated table's spelling or its slug. |
required |
source
|
str
|
The Xref source. Omitted, the species' symbol-carrying default answers. |
None
|
release
|
str
|
The pinned Release. Omitted, the newest that source has. Honoured against whichever source was filled in, exactly as on the ordinary constructor. |
None
|
cache_dir
|
str or Path
|
The directory to prepare in, as on the ordinary constructor. |
None
|
progressbar
|
bool
|
Show the download's progress bar. |
True
|
Returns:
| Type | Description |
|---|---|
XrefSet
|
The prepared set, which is an ordinary one in every respect — every verb answers on it and nothing about it remembers which question filled its source in. |
Raises:
| Type | Description |
|---|---|
NoXrefSetError
|
If no set exists for that species or release, or if no row for the species is flagged to answer symbols. The message names what does exist. |
Examples:
__len__ ¶
__repr__ ¶
Return which set this is and how many stems it holds.
The evidence filter appears only when there is one, so a filtered set never reads as the unfiltered set it is not.
to_stems ¶
Return the Gene id stems this release says each foreign id names.
The hop toward the hub. Every id is reduced to one spelling on the way in —
version dropped, the namespace's own CURIE prefix accepted whether or not it is
written — so ENSG00000141510.18 and ENSG00000141510, or HGNC:11998 and
11998, are one identifier and resolve identically. Joining a versioned id to a
bare one otherwise returns zero matches and says nothing, which is the most
error-prone detail in this landscape.
Every stem, and never a chosen one. A foreign id naming two stems answers with both: 2,535 of 40,665 human genes carry more than one Ensembl cross-reference in Alliance 9.0.0, so 6.2% of HGNC ids are ambiguous and nothing here picks a side.
Nothing is dropped. Ids this release names no stem for come back in
:attr:~ResolvedStems.unresolved, in ask order, so what a list holds and this
release does not is visible rather than silently shorter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
iterable of str
|
The foreign ids, in the order they should come back. Repeats are asked once, on the caller's own spelling, so a versioned and an unversioned spelling of one id are two entries with identical values and the answer still zips against the caller's table row for row. |
required |
namespace
|
str
|
The Namespace those ids belong to, one of :attr: |
required |
Returns:
| Type | Description |
|---|---|
ResolvedStems
|
The ids that named stems, mapped to every stem each names, and the ids that named none — with the species, source, release and namespace that answered. |
Raises:
| Type | Description |
|---|---|
NamespaceNotCarriedError
|
If this set carries no such namespace. The message names the ones it does. |
SymbolDirectionError
|
If the namespace is the symbol one. A symbol is not answered like an id — it
matches previous and alias spellings too, and each match carries which kind it
was — so the message names :meth: |
Examples:
from_stems ¶
Return the foreign ids this release says each Gene id stem names.
The hop away from the hub, and :meth:to_stems's mirror in every respect: the
same normalisation on the way in, every id and never a chosen one, and the stems
that named nothing riding back in ask order. A stem this release never carried and
a stem it carries with no id in this namespace are one bucket, since no id
history is held that could tell a retirement from an absence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stems
|
iterable of str
|
The stems, in the order they should come back. A versioned gene id is accepted and reduced to its stem, so an annotation's own ids may be passed straight in. |
required |
namespace
|
str
|
The Namespace to answer in, one of :attr: |
required |
Returns:
| Type | Description |
|---|---|
ResolvedXrefIds
|
The stems that named ids, mapped to every id each names, and the stems that named none — with the species, source, release and namespace that answered. |
Raises:
| Type | Description |
|---|---|
NamespaceNotCarriedError
|
If this set carries no such namespace. The message names the ones it does, and
for the symbol namespace it names this species' symbol source and
:meth: |
Examples:
match_symbols ¶
Return every gene this release says each symbol names, and how each matched.
The hop toward the hub from the one Namespace answered unlike the rest, and
deliberately not :meth:from_stems's mirror. A symbol is matched against
approved, previous and alias spellings, answers with every Gene id stem any
of them names, and each hit says which kind of spelling it was — so ambiguity is
the return type and not an edge case. ADCY3 is HGNC's approved symbol for one
gene and a symbol it retired from another, and both come back.
Exact by default. The species is fixed by the set, so Brca1 asked of a human
set is a mouse spelling asked of the wrong authority and matches nothing rather than
half-working. case_insensitive=True folds both sides and still answers with
every gene matched rather than picking one.
What this source could not have matched rides back on the answer, in
:attr:~ResolvedSymbols.kinds and :attr:~ResolvedSymbols.limits: mouse and worm
match approved spellings only, their authorities' typed previous and alias
spellings belonging to publishers that cannot be pinned or cannot be fetched, and an answer that did not say so would look exactly like a gene that
is not in the release.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbols
|
iterable of str
|
The symbols, in the order they should come back. Surrounding whitespace goes;
case does not, unless |
required |
case_insensitive
|
bool
|
Fold case on both sides — the caller's spelling and the authority's. |
False
|
Returns:
| Type | Description |
|---|---|
ResolvedSymbols
|
The symbols that matched, mapped to every match each made, and the symbols that matched nothing — with the species, source and release that answered, which kinds it could match and why the others are missing. |
Raises:
| Type | Description |
|---|---|
NamespaceNotCarriedError
|
If this set carries no symbols at all — which the species' identifier default
does not, for human. It raises rather than reaching for another publisher's
bytes, and the message names the one source that answers this species' symbols
and the constructor that fills it in, :meth: |
Examples:
XrefSetNotDownloadedError ¶
Bases: PreparedSetNotDownloadedError
The set is not on disk and could not be fetched, so nothing can answer.
The Xref context's own spelling of what every Prepared set raises here, so the
message names this set and quotes :func:xref_prepare_command; what the base class
says about a compute node with no internet is said once, there.
Examples:
XrefTableError ¶
Bases: ValueError
A table read here is not the shape it must be, so it is not allowed to answer.
Covers both files this module reads: the publisher's, when what arrived does not match
the checksum the curated row pins or carries no row for the species asked for; and the
stored slice, when its header, its columns or a Namespace in it is not what this
package writes. A :class:ValueError, because a file that says something the format
does not is a bad value rather than a broken program, and the message names the file
and the repair.
Examples:
normalise_evidence ¶
Return the evidence types asked for, in one canonical spelling.
Upper-cased because that is how every publisher surveyed writes them, stripped, emptied of blanks, deduplicated and sorted — the types are a set and their order carries no meaning, so sorting them is what lets the filter name a directory that two callers who asked in different orders both land on.
Idempotent, and total: anything that is not asked for comes back as the empty tuple, which is what no filter at all is spelled as everywhere below.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
evidence
|
str or iterable of str or None
|
One evidence type, several, or |
required |
Returns:
| Type | Description |
|---|---|
tuple of str
|
The types, upper-cased, unique and ascending. Empty when nothing was asked for. |
Examples:
gene_id_stem ¶
Return gene_id with its version dropped — its Gene id stem.
Everything before the first ., and the whole id when it carries none, which is what
makes an unversioned id its own stem. The Annotation half of this package reduces a
GTF's gene ids by the same rule, which is what lets a stem answered here be handed
straight to :meth:~genome.annotation.registry.AnnotationRegistry.resolve_gene_ids.
Idempotent: the stem of a stem is that stem.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gene_id
|
str
|
A gene id, versioned or not. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Its stem. |
Examples:
normalise_id ¶
Return xref_id in namespace's canonical spelling, version dropped.
The one door every identifier comes through, a publisher's and a caller's alike. Four
things happen, in this order: surrounding whitespace goes; every CURIE prefix the
namespace is published under is stripped, repeatedly, case-insensitively; the version
suffix goes (:func:gene_id_stem) and whitespace goes again; and the namespace's
own canonical prefix goes back on. So HGNC:1100, hgnc:1100 and 1100 are one
identifier, and ENSEMBL:ENSG00000141510.18 and ENSG00000141510 are another.
Idempotent, which is the property that matters: the same id read from a file and typed
by a caller must land on the same string, or the join returns nothing and says nothing.
The second strip is what makes that true rather than nearly true — a version separator
hides trailing whitespace behind it, so "7157\r." stems to "7157\r" on the
first pass and only reaches "7157" on the second, and two spellings of one id that
settle on different strings after a different number of passes join to nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xref_id
|
str
|
The identifier, in whatever spelling it arrived in. |
required |
namespace
|
str
|
The Namespace it belongs to, one of :data: |
required |
Returns:
| Type | Description |
|---|---|
str
|
The canonical spelling. Empty for an id that is empty or whitespace. |
Examples:
>>> normalise_id("HGNC:1100", "hgnc")
'HGNC:1100'
>>> normalise_id("1100", "hgnc")
'HGNC:1100'
>>> normalise_id("MGI:MGI:88276", "mgi")
'MGI:88276'
>>> normalise_id("UniProtKB:P38398", "uniprot")
'P38398'
>>> normalise_id(" ENSEMBL:ENSG00000141510.18 ", "ensembl")
'ENSG00000141510'
>>> normalise_id("7157\r.", "entrez")
'7157'
lookup_xref ¶
lookup_xref(
species: str,
source: str | None = None,
release: str | None = None,
*,
for_symbols: bool = False,
table: Sequence[XrefMetadata] | None = None,
) -> XrefMetadata
Return the curated row for one Xref set, filling in the defaults.
The one lookup, and the one place a miss is turned into an error that says what is
there. It is total in the other direction: it always answers with a row or raises, so
nothing downstream guards a None before reading a field.
A default is per species and per question, which is what for_symbols
selects. It changes what an unnamed source resolves to and nothing else: a named
source is the caller's deliberate scientific choice and is never swapped for another,
and a named release is still honoured against whichever source was filled in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the table's own spelling ( |
required |
source
|
str
|
The Xref source. Omitted, the species' Default xref source answers — the
one flagged for identifiers, or the one flagged for symbols when |
None
|
release
|
str
|
The Release. Omitted, the newest the table lists for that source answers —
which is the last row, the table being in release order. Named, it is honoured
whether or not a source was named too: a release asked for against the default
source either answers with that release or raises naming the ones that source has,
and is never quietly swapped for another. Each source numbers its own releases and
they do not correspond, so |
None
|
for_symbols
|
bool
|
Fill an unnamed source in with the species' symbol-carrying default rather than its identifier one. The question a caller is about to ask, named here because a row is resolved before anybody knows it — an Xref set is built for a species, a source and a release, and which of the two verbs it will be asked is not part of that. |
False
|
table
|
sequence of XrefMetadata
|
The rows to read; the shipped table when omitted. A caller curating rows of their own hands them over here, and nothing is installed by passing them. |
None
|
Returns:
| Type | Description |
|---|---|
XrefMetadata
|
The row, with |
Raises:
| Type | Description |
|---|---|
NoXrefSetError
|
If no set exists for that species, that source, or that release of it — or, under
|
Examples:
>>> lookup_xref("homo_sapiens").release
'9.0.0'
>>> lookup_xref("Homo sapiens", "alliance", "9.0.0").ncbi_taxid
9606
>>> lookup_xref("Homo sapiens", release="9.0.0").source
'alliance'
>>> lookup_xref("Homo sapiens", for_symbols=True).source
'hgnc'
>>> lookup_xref("Homo sapiens", "alliance", for_symbols=True).source
'alliance'
>>> try:
... lookup_xref("Homo sapiens", "alliance", "1.0")
... except NoXrefSetError as error:
... print("9.0.0" in str(error))
True
xref_releases ¶
xref_releases(
species: str,
source: str,
*,
table: Sequence[XrefMetadata] | None = None,
) -> tuple[str, ...]
Return every Release of source that answers for species, oldest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the table's spelling or its slug. |
required |
source
|
str
|
The Xref source. |
required |
table
|
sequence of XrefMetadata
|
The rows to read; the shipped table when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of str
|
The release strings in table order, which is oldest first — so the last is what a caller who names no release gets. |
Examples:
xref_sources ¶
Return every Xref source that answers for species, in first-listed order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the table's spelling or its slug. |
required |
table
|
sequence of XrefMetadata
|
The rows to read; the shipped table when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of str
|
The source names. Empty for a species no set exists for. |
Examples:
xref_species ¶
Return every species an Xref set exists for, as the table spells them.
What can be asked about at all, and what :class:NoXrefSetError names when a species
cannot be.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
sequence of XrefMetadata
|
The rows to read; the shipped table when omitted. |
None
|
Returns:
| Type | Description |
|---|---|
tuple of str
|
The species names, in first-listed order. |
Examples:
xref_table
cached
¶
Return every Xref set the shipped table lists, in table order.
Read once and cached; the records are frozen, so the tuple is safe to hold on to.
Rows for one (species, source) are in release order, oldest first.
Returns:
| Type | Description |
|---|---|
tuple of XrefMetadata
|
One record per row of |
Raises:
| Type | Description |
|---|---|
MetadataRowError
|
If the shipped file is empty, its header is not :data: |
Examples:
fold_symbol ¶
Return the key symbol is matched under when case is not to be significant.
:func:normalise_symbol and then :meth:str.casefold, which is the full-Unicode fold
rather than :meth:str.lower. Used only on the opt-in path, and used on both sides
of it — the caller's spelling and the authority's — so that folding is a property of
the lookup rather than of what was stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
The symbol, in any spelling. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Its case-folded key. Empty for an empty or all-whitespace symbol. |
Examples:
normalise_symbol ¶
Return symbol in the one spelling a match is looked up under.
Surrounding whitespace and nothing else. A symbol is not put through
:func:~genome.xref.ids.normalise_id: that drops everything after the first .,
which is right for a versioned gene id and wrong for a symbol — WormBase names
thousands of genes by their sequence name, and Y110A7A.10 stemmed to Y110A7A
is a different gene's spelling or none at all.
Idempotent, and total: an empty or all-whitespace symbol comes back empty and matches nothing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
str
|
The symbol, as a caller typed it or a publisher wrote it. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The symbol with surrounding whitespace removed. Case is untouched, because case is significant: exact matching is the default and a mouse-cased spelling asked of a human set is the wrong authority's, not a typo to absorb. |
Examples:
xref_data_dir ¶
Return the directory holding Xref sets, which belong to no Assembly.
The Xref context's own root under the Data dir, declared here because this is where its Prepared set is fetched into and read from.
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples:
xref_prepare_command ¶
xref_prepare_command(
species: str,
source: str,
release: str,
*,
evidence: Sequence[str] = (),
) -> str
Return the call that prepares one Xref set, for an error message to quote.
One spelling of it, so a renamed entry point is renamed once. Quoted by every error here that a caller repairs by fetching the set on a machine with internet — which is why an evidence filter travels in it: repairing a filtered set by preparing the unfiltered one would leave the caller exactly where they started.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, as the curated table spells it. |
required |
source
|
str
|
The Xref source. |
required |
release
|
str
|
The pinned Release. |
required |
evidence
|
sequence of str
|
The evidence filter the set was built under. Omitted from the command when empty. |
()
|
Returns:
| Type | Description |
|---|---|
str
|
A shell command, unquoted and unfenced — the caller decides how to set it. |
Examples:
xref_set_dir ¶
Return the directory one Xref set is prepared in, whether or not it exists.
<liulab_data>/xref/<source>/<release>/<species slug>/. Source and release above
species, so two releases of one publisher sit side by side and neither is the xref
directory — holding two releases at once is the whole point of pinning one.
A set built under an evidence filter is a different set and gets a directory of its own beside the unfiltered one, since a filter that changes which rows are read changes what the stored slice holds. Two callers who named the same types in different orders land on one directory, the filter being sorted on the way in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the curated table's spelling or its slug. |
required |
source
|
str
|
The Xref source. |
required |
release
|
str
|
The pinned Release. |
required |
evidence
|
sequence of str
|
The evidence filter the set was built under, as
:func: |
()
|
Returns:
| Type | Description |
|---|---|
Path
|
The set's own directory. Nothing is created by asking. |
Examples:
>>> import os
>>> os.environ["LIULAB_DATA"] = "/scratch/liulab"
>>> xref_set_dir("Homo sapiens", "alliance", "9.0.0")
PosixPath('/scratch/liulab/xref/alliance/9.0.0/homo_sapiens')
>>> xref_set_dir("Homo sapiens", "ensembl", "116", evidence=("DEPENDENT",))
PosixPath('/scratch/liulab/xref/ensembl/116/homo_sapiens.evidence-dependent')
>>> del os.environ["LIULAB_DATA"]
xref_slice_name ¶
Return the file name one species' stored slice is written under.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
str
|
The species, in either the curated table's spelling or its slug. |
required |
Returns:
| Type | Description |
|---|---|
str
|
|
Examples: