API reference¶
Generated from the docstrings in the installed package, so this page cannot drift from the code. The sibling sites hand-write their reference pages; gffbase exports 90+ public symbols whose docstrings are themselves executed as tests, and a hand-written copy of that surface starts going stale the day it is written.
Name-mangled internals are omitted. Everything shown here is public API and is covered by the compatibility guarantees in Migrating from gffutils to gffbase.
The database¶
FeatureDB is the handle returned by gffbase.create_db() and is where
almost every query lives.
- class gffbase.interface.FeatureDB(dbfn, default_encoding: str = 'utf-8', keep_order: bool = False, pragmas: dict | None = None, sort_attribute_values: bool = False, text_factory=<class 'str'>, upgrade: str = 'auto', read_only: bool = False, _own_conn: bool | None = None)[source]¶
Bases:
objectDrop-in successor to
gffutils.FeatureDB.- warnings: list[dict]¶
Specification violations tolerated while building this database. Populated under
mode="compat"; empty for a reopened database, which has no record of how it was built.
- schema() str[source]¶
The database schema as SQL text.
A METHOD, not a property: gffutils documents
db.schema()and callers write it that way. Exposing it as a property meant the documented call raisedTypeError: 'str' object is not callable.
- close() None[source]¶
Release the DuckDB connection. Idempotent.
The lazily-created segment cursor is closed whether or not the connection is owned – gffbase created it via
conn.cursor(), so gffbase closes it. The connection itself is closed only when this handle opened it: a caller who passed their own connection in still holds it afterwards.
- validate(level: str = 'fast', **kwargs)[source]¶
Check this database’s structural invariants.
See
gffbase.validate.validate_db(). Run automatically at the end of a strict-mode ingest; worth running by hand afterupdate(),delete()orcoalesce_multipart(), which are the operations that can leave the two halves of a discontinuous feature disagreeing.
- count_features_of_type(featuretype: str | None = None) int[source]¶
Count features, optionally of one type.
- Parameters:
featuretype – Restrict the count to this GFF column-3 value (
"gene","exon", …).Nonecounts every feature.- Returns:
The number of matching features. A discontinuous feature counts once, however many input lines it was built from.
Example:
db.count_features_of_type() # 6 db.count_features_of_type("exon") # 3
- featuretypes() Iterator[str][source]¶
Yield every distinct featuretype in the database, alphabetically.
- Yields:
Each distinct GFF column-3 value, once.
Example:
sorted(db.featuretypes()) # ['CDS', 'exon', 'gene', 'mRNA']
- seqids() Iterator[str][source]¶
Yield every distinct sequence id in the database, alphabetically.
Useful for checking naming convention before a
region()query –chr1,1andNC_000001.11are three different sequences as far as the database is concerned, and GENCODE, Ensembl and RefSeq each pick a different one.- Yields:
Each distinct GFF column-1 value, once.
- all_features(limit: str | tuple | Feature | None = None, strand: str | None = None, featuretype: str | list[str] | None = None, order_by: str | None = None, reverse: bool = False, completely_within: bool = False) Iterator[Feature][source]¶
Iterate over every feature in the database.
- Parameters:
limit – Restrict to a genomic region, as
"seqid:start-end"or a(seqid, start, end)tuple.Nonescans everything.strand – Restrict to
"+","-"or".".featuretype – One featuretype, or a list of them.
order_by – Column to sort by. One of
id,seqid,source,featuretype,start,end,score,strand,frame,attributes,extra,file_order,length. Anything else raisesValueError– this is a whitelist, not a SQL fragment.reverse – Sort descending.
completely_within – With
limit, return only features contained entirely inside the region rather than merely overlapping it.
- Yields:
Featureobjects infile_orderunlessorder_bysays otherwise.- Raises:
ValueError –
order_bynames something outside the whitelist.
Example:
for feature in db.all_features(featuretype="exon", order_by="start"): print(feature.id, feature.start)
- method(*args, **kwargs) Iterator[Feature][source]¶
Alias for
all_features, kept because gffutils defines one.
- features_of_type(featuretype: str | list[str], limit: str | tuple | Feature | None = None, strand: str | None = None, order_by: str | None = None, reverse: bool = False, completely_within: bool = False) Iterator[Feature][source]¶
Iterate over every feature of one type (or several).
Equivalent to
all_features(featuretype=...); both exist because gffutils has both.- Parameters:
featuretype – One featuretype (
"exon"), or a list of them.limit – Restrict to a genomic region – see
all_features.strand – Restrict to
"+","-"or".".order_by – Column to sort by – see
all_featuresfor the permitted names.reverse – Sort descending.
completely_within – With
limit, require full containment.
- Yields:
Matching
Featureobjects.
Example:
genes = list(db.features_of_type("gene")) both = list(db.features_of_type(["exon", "CDS"]))
- region(region=None, seqid: str | None = None, start: int | None = None, end: int | None = None, strand: str | None = None, featuretype: str | list[str] | None = None, completely_within: bool = False) Iterator[Feature][source]¶
Features overlapping a genomic interval.
The interval can be given as a string, a tuple, or the three keyword arguments:
db.region("chr1:1000-2000") db.region(("chr1", 1000, 2000)) db.region(seqid="chr1", start=1000, end=2000)
- Parameters:
region (str or tuple) –
"chrom:start-stop", or(chrom, start, stop).seqid (optional) – The same interval, spelled out. Mutually exclusive with
region.start (optional) – The same interval, spelled out. Mutually exclusive with
region.end (optional) – The same interval, spelled out. Mutually exclusive with
region.strand ({"+", "-", "."}, optional) – Restrict to one orientation.
featuretype (str or list of str, optional) – Restrict to one or several types.
completely_within (bool) – False (default) returns anything that OVERLAPS the interval; True returns only features contained entirely within it.
here (Which index answers the query is chosen)
caller (not by the)
specified (R-tree when one was built and the interval is fully)
the
-- (tests/test_spatial_parity.py asserts they return the same features)
--
decision (so this is a planner)
one. (not a behavioural)
and (A feature with no coordinates (a GFF row carrying . in columns 4)
returned. (5) is not in coordinate space and is never)
- to_table(featuretype: str | list[str] | None = None, *, format: str = 'arrow', limit: str | tuple | Feature | None = None, strand: str | None = None, order_by: str | None = None, reverse: bool = False, completely_within: bool = False) Any[source]¶
Return the whole database (or a slice of it) as one table.
The columnar counterpart to
all_features(): same filters, but the result is apyarrow.Table/pandas.DataFrame/polars.DataFrameinstead of a stream ofFeatureobjects, and noFeatureis constructed at any layer.For “give me every exon as a dataframe”, the row-by-row API pays a Python object per row – millions of them on a whole-genome corpus – and that allocation dominates everything else. This is one query and one hand-off.
- Parameters:
featuretype – One featuretype, or a list of them.
Noneis everything.format –
"arrow"(default, zero-copy),"df"for pandas, or"polars".limit – Restrict to a genomic region, as
"seqid:start-end"or a(seqid, start, end)tuple.strand – Restrict to
"+","-"or".".order_by – Column to sort by – the same whitelist
all_featuresaccepts.reverse – Sort descending.
completely_within – With
limit, require full containment.
- Returns:
A table in the shape named by
format, one row per feature.
Columns are those of the
featurestable:id, seqid, source, featuretype, start, end, score, strand, frame, file_order
- Raises:
ValueError –
order_bynames something outside the whitelist, orformatis not one of the three.ImportError –
format="df"/"polars"without that package.
Example:
exons = db.to_table("exon", format="arrow") df = db.to_table(["exon", "CDS"], format="df", limit="chr1:1-10000")
Note
Attributes are not included: they are a long-form table, so flattening them would either invent a column per key or collapse multi-valued keys. Query the
attributestable withexecute()when you need them.
- region_batched(regions, featuretype: str | list[str] | None = None, completely_within: bool = False, format: str = 'arrow', explode_segments: bool = False, on_invalid: str = 'raise')[source]¶
Bulk overlap query. Performs a SINGLE spatial JOIN between every input region and the features table, returning a column-oriented result that maps each query (
query_idx) back to its overlapping features.query_idxindexesregionsas you passed it. That is the whole contract of the column – it is how a caller reassembles per-query groups without re-issuing N queries.- Parameters:
regions (iterable of (seqid, start, end) | str | Feature) – Each item is normalized through
_normalize_region_args; the same four input shapes accepted byregion()are accepted here.featuretype (str | list[str] | None) – Optional
features.featuretypefilter applied to all regions.completely_within (bool) – If True, only features fully contained in the region are returned (default False — overlap is sufficient).
format ("arrow" | "df" | "polars") – Return shape (default
"arrow").on_invalid ({"raise", "skip"}) – What to do with an item that does not normalize to a region.
"raise"(default) raisesValueErrornaming the offending position and value."skip"drops it while leaving every other item’squery_idxat its position inregions, so the gap is visible rather than silently closed up.explode_segments (bool) –
Emit one row per physical INPUT LINE rather than one per logical feature, adding a
seg_idxcolumn. A discontinuous feature then contributes a row per segment, each with its own coordinates, score and phase – which is what a caller writing a coverage track or exporting to a line-oriented format actually needs.Offered on the tabular APIs only, never on
region()/children()/all_features(): those must keep yieldingFeatureobjects, and lettingFeatureSegmentrows leak into them would corrupt legacy consumers.columns:: (Result) – query_idx, query_seqid, query_start, query_end, id, seqid, source, featuretype, start, end, score, strand, frame, file_order – plus seg_idx when explode_segments is set.
- children(id, level: int | None = None, featuretype: str | list[str] | None = None, order_by=None, reverse: bool = False, limit=None, completely_within: bool = False) Iterator[Feature][source]¶
Descendants of a feature, in hierarchy order.
- Parameters:
id (str or Feature) – The anchor. A
Featurebuilt by hand hasid is Noneand is rejected rather than silently matching nothing.level (int, optional) – Only descendants exactly this many steps down – 1 is direct children. None (default) walks the whole subtree.
order_by (str or sequence of str, optional) – A whitelisted sort key; anything else raises
ValueErrornaming the accepted set. Seedocs/source/content/advisory_sql_injection.rstfor why this is not a free-text field.reverse (bool) – Applies to every key, not just the last.
limit (str or tuple, optional) – Restrict to a genomic interval, as
region()accepts it.completely_within (bool) – With
limit, require containment rather than overlap.the (Reads from the materialized closure table when level is within)
depth (database's recorded)
a (and falls back to a recursive CTE when)
cycle-safe (deeper walk is asked for. Both are)
once (containing a loop is traversed)
runs (not until the depth budget)
out.
- parents(id, level: int | None = None, featuretype: str | list[str] | None = None, order_by=None, reverse: bool = False, completely_within: bool = False, limit=None) Iterator[Feature][source]¶
Ancestors of a feature, in hierarchy order.
The mirror of
children(), taking the same arguments and making the same routing decision.level=1is direct parents.GFF3 permits a feature to name several
Parentattributes, so the hierarchy is a DAG rather than a tree and one ancestor can be reachable by more than one path. Each is returned once.
- children_batched(feature_ids, level: int | None = None, featuretype: str | list[str] | None = None, format: str = 'arrow', explode_segments: bool = False)[source]¶
Bulk children lookup. Returns the descendants of ALL
feature_idsin a single vectorized DuckDB query.- Parameters:
feature_ids (iterable of str | Feature) – Anchors. May contain
Featureobjects or raw ID strings.level (int | None) – None → all descendants (closure cache when materialized, otherwise dynamic CTE). Integer → exact-depth point lookup.
featuretype (str | list[str] | None) – Optional filter on
features.featuretype.format ("arrow" | "df" | "polars") – Return shape (default
"arrow"–pyarrow.Table).explode_segments (bool) – Emit one row per physical INPUT LINE rather than one per logical feature, adding a
seg_idxcolumn. Tabular APIs only – seeregion_batched().columns:: (Result) – anchor (the parent ID supplied), descendant_id, seqid, source, featuretype, start, end, score, strand, frame, file_order, depth – plus seg_idx when
explode_segments.
- parents_batched(feature_ids, level: int | None = None, featuretype: str | list[str] | None = None, format: str = 'arrow', explode_segments: bool = False)[source]¶
Bulk parents lookup. Mirrors
children_batchedbut walks the closure / edges in the reverse direction.
- delete(features: str | Feature | Iterable[str | Feature], make_backup: bool = True, **kwargs) FeatureDB[source]¶
Delete features, and everything that referenced them.
Removes the rows from
features,attributes,segmentsandedges, then rebuilds the transitive closure so no path through a deleted node survives – deleting a transcript really does remove its exons from its gene’s descendants.- Parameters:
features – A feature id, a
Feature, or an iterable of either.make_backup – Accepted for gffutils compatibility and currently ignored; no
.bakis written.
- Returns:
self, so calls can be chained.- Raises:
ReadOnlyError – The database was opened with
read_only=True.
Example:
db.delete("transcript_1") db.delete([f.id for f in db.features_of_type("CDS")])
- update(data: Iterable, make_backup: bool = True, **kwargs) FeatureDB[source]¶
Add features to an existing database.
Appends to
features,attributesandedges, then rebuilds the transitive closure and re-reads the corpus statistics the relational dispatcher routes on.- Parameters:
data – An iterable of
FeatureorParsedFeatureobjects, or anotherFeatureDBwhose features are copied in.make_backup – Accepted for gffutils compatibility and currently ignored; no
.bakis written.
- Returns:
self, so calls can be chained.- Raises:
ReadOnlyError – The database was opened with
read_only=True.TypeError – An item is neither a
Featurenor aParsedFeature.
Example:
introns = list(db.create_introns()) db.update(introns)
- add_relation(parent, child, level: int = 1, parent_func=None, child_func=None) FeatureDB[source]¶
Link one parent to one child.
parent_func/child_funcreceive(parent, child), and whatever they RETURN is written back to the database. That is upstream’s contract and the reasonassign_childreturns the child: the callback exists to edit an attribute (Parent=) that then has to be persisted. Previously the return value was discarded and nothing was written, sochild_func=assign_childset an attribute on a throwaway object.A string id is resolved to a
Featurefirst, so callbacks fire whether the caller passed objects or ids – they used to be skipped silently for ids.
- add_relations(pairs, level: int = 1, parent_func=None, child_func=None) FeatureDB[source]¶
add_relationfor many pairs, with ONE closure rebuild.Deriving the closure costs a recursive CTE over every edge, so doing it per pair makes a bulk operation quadratic.
merge_alllinks every component of every merged feature and is the caller that made this necessary.
- interfeatures(features, new_featuretype=None, merge_attributes: bool = True, numeric_sort: bool = False, dialect=None, attribute_func=None, update_attributes=None)[source]¶
The gaps between consecutive features.
attribute_functakes ONE argument – an attribute mapping – and returns one, matching the oracle. It is applied to each neighbour’s attributes before they are merged, not to the merged result. The previous three-argument(prev, cur, attrs)form meant any gffutils caller passing a callback got aTypeError.
- property derived_source: str¶
sourcefor features this database derives rather than reads.gffutils_derivedunder compat so ported scripts that filter on it keep working;gffbase_derivedunder strict, which reports honest provenance.
- merge(features, merge_criteria=None, multiline: bool = False)[source]¶
Collapse runs of features that satisfy every criterion.
Consumes
featuresin the order given. That is the oracle’s contract and it matters:merge_allsupplies a specificmerge_order, and re-sorting here (which this used to do) silently discarded it.A feature that merged with nothing is yielded unchanged with
childrenset tono_children, so a caller can tell a real merge from a pass-through by truthiness.merge_alldepends on exactly that.
- merge_all(merge_order=('seqid', 'featuretype', 'strand', 'start'), merge_criteria=None, featuretypes_groups=(None,), exclude_components: bool = False) list[Feature][source]¶
Merge everything in the database and write the results back.
Three things were wrong here and all three were silent. The method returned every input feature including ones that merged with nothing, so a caller could not tell what had actually been merged; it persisted nothing, despite documenting that “the resulting records are added to the database”; and it accepted
exclude_componentsand ignored it, so asking for the components to be removed did nothing at all.
- create_introns(exon_featuretype: str = 'exon', grandparent_featuretype: str | None = 'gene', parent_featuretype: str | None = None, new_featuretype: str = 'intron', merge_attributes: bool = True, numeric_sort: bool = False) Iterator[Feature][source]¶
Introns, computed per transcript.
grandparent_featuretype="gene"descends one level first and computes the gaps within each transcript separately. Treating the gene as the direct anchor – which this used to do – pools the exons of every isoform into one sorted list, so the “introns” of a multi-isoform gene were computed across transcript boundaries and were not introns of anything.
- create_splice_sites(exon_featuretype: str = 'exon', grandparent_featuretype: str | None = 'gene', parent_featuretype: str | None = None, merge_attributes: bool = True, numeric_sort: bool = False) Iterator[Feature][source]¶
The two-base splice sites flanking each intron.
A splice site is a dinucleotide – GT at the donor, AG at the acceptor – so these are 2 bp features, not the 1 bp ones this used to emit. They are typed by their position in the transcript rather than in the genome, so the left site of a minus-strand transcript is its 3’ site.
Emission order is every left site, then every right site, matching the oracle; the intron’s merged attributes are carried through with the ID prefixed by the featuretype so the two sites of one intron differ.
- children_bp(feature: str | Feature, child_featuretype: str = 'exon', merge: bool = False, merge_criteria: Sequence | None = None, **kwargs) int[source]¶
Total base pairs covered by a feature’s children.
- Parameters:
feature – The parent, as an id or a
Feature.child_featuretype – Which children to measure.
merge – Merge overlapping children first, so shared bases are counted once. Without it, overlapping children double-count.
merge_criteria – Predicates controlling what may merge; see
gffbase.merge_criteria. Defaults to same seqid, strand and featuretype with inclusive overlap.
- Returns:
The summed length in base pairs.
- Raises:
ValueError – The removed
ignore_strandargument was passed.TypeError – Any other unexpected keyword argument.
Example:
db.children_bp("transcript_1", child_featuretype="exon", merge=True)
- bed12(feature: str | Feature, block_featuretype: Sequence[str] = ('exon',), thick_featuretype: Sequence[str] = ('CDS',), thin_featuretype: Sequence[str] | None = None, name_field: str = 'ID', color: str | None = None) str[source]¶
Render a feature and its children as one BED12 line.
- Parameters:
feature – The parent, as an id or a
Feature.block_featuretype – Child types that become BED blocks (exons).
thick_featuretype – Child types that define the thick region (coding sequence).
thin_featuretype – Child types that define the thin region. When given, it is honoured rather than inferred.
name_field – Attribute used for BED column 4. Falls back to the feature id when absent.
color – RGB string for column 9, e.g.
"255,0,0".
- Returns:
A tab-separated BED12 line, without a trailing newline.
Note
A feature with no thick children is emitted as entirely thick.
blockSizesandblockStartscarry no trailing comma.Example:
print(db.bed12("transcript_1"))
- iter_by_parent_childs(featuretype: str = 'gene', level: int | None = None, order_by: str | None = None, reverse: bool = False, completely_within: bool = False) Iterator[list[Feature]][source]¶
Group the database by parent, yielding one list per parent.
- Parameters:
featuretype – The parent featuretype to group by.
level – How deep to collect children.
Nonetakes the whole subtree;1takes direct children only.order_by – Column to sort parents by – see
all_features.reverse – Sort parents descending.
completely_within – Passed through to the child query.
- Yields:
A list per parent, the parent first followed by its children.
Example:
for group in db.iter_by_parent_childs("gene"): gene, children = group[0], group[1:]
- attribute_search(text: str, featuretype=None) Iterator[Feature][source]¶
Features with an attribute VALUE matching
text, case-insensitively.textis SQLLIKEsyntax, so%and_are wildcards; a bare string matches as a substring, which is what a caller searching for an accession expects.gffutils’ CLI calls
db.attribute_search(...), but no such method exists anywhere in gffutils – only in that call site and in an obsolete test file – sogffutils-cli searchraisesAttributeErroron every invocation. This is a working implementation rather than a port of one.The search is over the long-form
attributestable, not over the raw column-9 blob, so it matches DECODED values: searching fora;bfinds a feature whose source saida%3Bb.
- execute(query: str)[source]¶
Execute arbitrary SQL. Returns DuckDB’s relation cursor. SQLite-style queries against
features_compatandrelations_compatviews are supported; seecompat_views.sql.Deliberately NOT guarded against
read_only: this is the escape hatch, the SQL is the caller’s, and DuckDB’s own refusal names the statement it rejected, which is more use here than a generic message from us.
- analyze() None[source]¶
Refresh DuckDB’s planner statistics for this database.
Worth running once after a large
update(); the planner otherwise keeps costing queries against the shape the database had at ingest.- Raises:
ReadOnlyError – The database was opened with
read_only=True.
- set_pragmas(pragmas: dict) None[source]¶
Apply DuckDB settings, ignoring pragmas that only SQLite has.
Legacy callers pass
constants.default_pragmas–synchronous,journal_mode,main.page_size,main.cache_size– none of which DuckDB has. Those are skipped, which is what makes a gffutils script run here unchanged.Both the name and the value used to be interpolated straight into the statement, and the whole loop body sat inside
except duckdb.Error: continue. DuckDB executes trailing statements, sodb.set_pragmas({“threads”: “1; DROP TABLE attributes”})
dropped the table – and because the exception was swallowed, a payload that failed was silent too. Names are now matched against DuckDB’s own settings catalog and values rendered as SQL literals, so nothing a caller supplies reaches the parser as syntax.
Matching the live catalog rather than a hardcoded list means the check tracks whatever DuckDB build is installed, instead of going stale and rejecting settings a newer version added.
Building a database¶
- gffbase.create_db.create_db(data, dbfn, id_spec=None, force=False, verbose=False, checklines=10, merge_strategy='error', transform=None, gtf_transcript_key='transcript_id', gtf_gene_key='gene_id', gtf_subfeature='exon', force_gff=False, force_dialect_check=False, from_string=False, keep_order=False, text_factory=<class 'str'>, force_merge_fields=None, pragmas=None, sort_attribute_values=False, dialect=None, _keep_tempfiles=False, infer_gene_extent=True, disable_infer_genes=False, disable_infer_transcripts=False, mode='compat', validation=None, on_error=None, on_multipart_conflict='error', **kwargs) FeatureDB[source]¶
Create a database from a GFF3/GTF source.
- Parameters:
data – Path to a GFF3/GTF file (optionally gzipped), or the file contents themselves when
from_string=True.dbfn – Destination database path, or
":memory:".id_spec – Primary-key policy.
Noneuses the per-dialect default:"ID"for GFF3,{"gene": "gene_id", "transcript": "transcript_id"}for GTF. May also be an attribute name, an ordered list of names (first match wins), afeaturetype -> namemapping, or a callable returning an id (or"autoincrement:BASE"). A name of the form":seqid:"reads that GFF column instead of an attribute.force – Overwrite
dbfnif it exists. Without it, an existing file raises.verbose – Progress reporting.
"debug"selects DEBUG level.checklines – Lines sampled to infer the dialect.
merge_strategy – What to do when two features resolve to the same id:
"error","warning","merge","create_unique"or"replace".transform – Callable applied to each feature. Returning anything falsy drops it.
gtf_transcript_key – Attribute names used to reconstruct GTF hierarchy.
gtf_gene_key – Attribute names used to reconstruct GTF hierarchy.
gtf_subfeature – Attribute names used to reconstruct GTF hierarchy.
force_gff – Skip format autodetection and treat the input as GFF.
force_dialect_check – Infer the dialect from every line rather than the first
checklines. Mutually exclusive withdialect.from_string – Treat
dataas file contents rather than a path.keep_order – Preserve attribute order when features are rendered.
text_factory – Text coercion applied to values read back out.
force_merge_fields – With
merge_strategy="merge", fields allowed to differ and be combined.start/endare rejected: they must stay numeric.pragmas – Database pragmas.
sort_attribute_values – Sort attribute values when features are rendered.
dialect – Explicit dialect, bypassing inference.
infer_gene_extent – Deprecated.
Falsesets bothdisable_infer_*flags.disable_infer_genes – Skip synthesizing GTF gene/transcript rows from their children.
disable_infer_transcripts – Skip synthesizing GTF gene/transcript rows from their children.
mode ({"compat", "strict"}) –
"compat"(default) applies gffutils’ rule set, so files that break the GFF3 specification load exactly as they do under gffutils, with every violation recorded inFeatureDB.warnings."strict"applies the full NCBI specification and rejects violations."strict"is also the only mode that fuses several lines sharing oneIDinto a single discontinuous feature. That is deliberate: it is new behaviour rather than gffutils behaviour, since gffutils’merge_strategy="merge"requires all eight non-attribute columns to match and so never merges a genuine split feature.validation ({"gffutils", "ncbi"} | None) – Which rule set to apply, overriding the one
modeimplies.None(default) takes the mode’s. Use it to keep gffutils-compatible handling while applying the full NCBI rules – the combinationvalidation="ncbi", on_error="warn"audits a file without stopping on it, leaving every violation inFeatureDB.warnings.on_error ({"raise", "warn"} | None) – What a rejected line does, overriding the one
modeimplies."raise"stops at the first violation;"warn"records it and carries on.None(default) takes the mode’s.on_multipart_conflict ({"error", "split"}) – Under
mode="strict", what to do when lines sharing anIDdisagree on seqid, source, featuretype or strand – which GFF3 requires the segments of a discontinuous feature to share."error"(default) raisesMultipartConstraintErrornaming the diverging column and both line numbers;"split"partitions the run by those four columns, the lowestfile_orderkeeping the bare id.
- Return type:
- gffbase.ingest.from_file(path: str, dbfn: str = ':memory:', **kwargs) tuple[DuckDBPyConnection, IngestStats][source]¶
Ingest a GFF3 or GTF file into a DuckDB database, atomically.
The destination either does not exist or is a complete database: the ingest writes to a scratch file beside it and renames on success, so a failure part-way leaves nothing behind and an existing database is not destroyed until its replacement is finished.
Before this, a failed ingest left a file that was valid DuckDB but held no data and no
metarows. Retrying then refused with “already exists. Pass force=True”, and opening it silently produced an empty database that reported itself as current – the two ways this could mislead someone were to make them force-overwrite something, or to believe their data had loaded.See
_build_databasefor the ingest itself and the full argument list.
- class gffbase.ingest.IngestStats(n_features_raw: int = 0, n_features_synthetic_transcripts: int = 0, n_features_synthetic_genes: int = 0, n_attributes: int = 0, n_edges: int = 0, n_closure_rows: int = 0, rtree_built: bool = False, fmt: str = 'gff3', dialect: dict = None, directives: list[str] = None, warnings: list[dict] = None, n_skipped: int = 0, n_multipart: int = 0)[source]¶
Bases:
objectReported back to the caller for benchmarking and tests.
Features¶
- class gffbase.feature.Feature(seqid: str = '.', source: str = '.', featuretype: str = '.', start='.', end='.', score: str = '.', strand: str = '.', frame: str = '.', attributes=None, extra=None, bin: int | None = None, id: str | None = None, dialect: dict | None = None, file_order: int | None = None, keep_order: bool = False, sort_attribute_values: bool = False)[source]¶
Bases:
objectBackward-compatible public Feature object.
Mirrors the legacy
gffutils.Featureconstructor and observable behavior: 1-based inclusive coordinates, list-wrapped multi-value attributes, dialect-faithful__str__round-trip.- is_multipart: ClassVar[bool] = False¶
False for every ordinary feature.
MultipartFeatureoverrides it. A ClassVar rather than an instance attribute so the singleton case – which is every feature in almost every file – costs nothing per object.
- n_segments: int = 1¶
How many physical input lines this feature was built from.
A class attribute, so an ordinary feature carries no per-instance cost for it – but deliberately NOT a
ClassVar, becauseMultipartFeatureshadows it with a real slot and assigns per instance. Declaring itClassVarwould make that assignment a type error.
- seqid¶
- source¶
- featuretype¶
- start¶
- end¶
- score¶
- strand¶
- frame¶
- bin¶
- id¶
- dialect¶
- file_order¶
- keep_order¶
- sort_attribute_values¶
- attributes¶
- extra¶
- property chrom: str¶
Alias for
seqid(GFF column 1), the name gffutils uses.Reading and writing either name affects the same underlying value.
- property stop: int | None¶
Alias for
end(GFF column 5), the name gffutils uses.Nonewhen the source line carried.– such a feature has no coordinates and is skipped byregion().
- property segments: tuple[FeatureSegment, ...]¶
This feature’s physical input lines.
An ordinary feature is its own sole segment, so callers can write
for seg in feature.segmentswithout first asking whether the feature is discontinuous.
- to_line(normalized: bool = False) str[source]¶
Render this feature as one GFF line.
By default this is byte-faithful: if the original column 9 was never parsed or mutated, its bytes are re-emitted verbatim, so a file that round-trips through gffbase comes back unchanged. That is what
str(feature)does too.normalized=Trueinstead re-renders column 9 from the parsed attribute mapping, applying the dialect’s separators and thesort_attribute_valuessetting. This is what gffutils always does, so it is the form to use when comparing against the oracle – at the cost of losing whatever the source file’s exact spacing was.Values are percent-encoded on this path, so a value containing
;,,,=,&or%re-emits as valid GFF3. Spaces and non-ASCII are left alone, which is what the spec says and what the oracle does.
- to_lines(normalized: bool = False) list[str][source]¶
Every physical line of this feature. One, unless it is multipart.
- astuple(encoding=None)[source]¶
Legacy 12-tuple shape used by the SQLite export path.
The elements, in order:
id, seqid, source, featuretype, start, end, score, strand, frame, attributes_json, extra_json, bin
- class gffbase.feature.MultipartFeature(*args, n_segments: int = 1, segments=None, segment_loader=None, **kwargs)[source]¶
Bases:
FeatureA logical feature assembled from more than one input line.
Its own
start/endare the ENVELOPE –MIN(segment.start)andMAX(segment.end)– and every inherited method operates on that envelope, so a caller that knows nothing about discontinuous features sees exactly the gffutils behaviour for a feature spanning that range.Consequently
len(f)is the envelope span, matchingFeature.covered_lengthis the different, new quantity.- is_multipart: ClassVar[bool] = True¶
False for every ordinary feature.
MultipartFeatureoverrides it. A ClassVar rather than an instance attribute so the singleton case – which is every feature in almost every file – costs nothing per object.
- n_segments: int¶
How many physical input lines this feature was built from.
A class attribute, so an ordinary feature carries no per-instance cost for it – but deliberately NOT a
ClassVar, becauseMultipartFeatureshadows it with a real slot and assigns per instance. Declaring itClassVarwould make that assignment a type error.
- property segments: tuple[FeatureSegment, ...]¶
This feature’s physical input lines.
An ordinary feature is its own sole segment, so callers can write
for seg in feature.segmentswithout first asking whether the feature is discontinuous.
- property covered_length: int¶
Total length actually covered, with the gaps excluded.
Differs from
len(self), which is the envelope span. For a CDS split across two 100 bp exons 700 bp apart,lenis 900 and this is 200.Segments of a discontinuous feature must not overlap; if a malformed file provides overlapping ones, the shared bases are counted twice.
- class gffbase.feature.FeatureSegment(*args, seg_idx: int = 0, **kwargs)[source]¶
Bases:
FeatureOne physical input line of a discontinuous feature.
Carries its OWN coordinates, score, phase and column 9 – per-segment CDS phase is the main reason the storage exists – while
seqid,source,featuretypeandstrandcome from the logical feature, which by definition shares them.self.idis the LOGICAL id, sodb[seg.id]finds the whole feature. The segment’s ownID=is preserved byte-for-byte in the attributes blob, sostr(segment)reproduces the input line exactly.- seg_idx¶
0-based position in FILE order, not coordinate order. GFF3 does not require segments to be sorted, and
to_lines()has to reproduce the input.
- class gffbase.feature.ParsedFeature(seqid: 'str', source: 'str', featuretype: 'str', start: 'int | None', end: 'int | None', score: 'str', strand: 'str', frame: 'str', attributes_blob: 'bytes', attributes_pairs: 'list[tuple[str, str, int]]'=<factory>, extra: 'list[str]' = <factory>)[source]
Bases:
objectThe immutable parse result the engines hand to the ingest layer. Its fields mirror
Feature’s and are not repeated here: documenting both made every bare reference toseqid,startorendambiguous across the two classes, which is a warning under-Wand a coin-flip link for a reader.- seqid: str
- source: str
- featuretype: str
- score: str
- strand: str
- frame: str
- attributes_blob: bytes
- property chrom: str
Alias for
seqid, the name gffutils uses.
- attributes_dict() dict[source]
Materialize attributes as
{key: [values...]}. Preserves first-seen key order and multi-value ordering. Defers toattributes_pairsso the Rust and Python parsers remain trivially comparable.
- classmethod from_tuple(tup) ParsedFeature[source]
Build from the 11-tuple shape that the Rust extension yields.
The fields are stored as handed over.
rust/src/lib.rsalready builds aPyBytesfor the blob, aPyListofPyTuple(str, str, int)for the pairs, and a freshPyListforextra– so the coercions that used to sit here (bytes(blob),int(i)in a rebuilt list comprehension,list(extra)) never converted anything. They allocated a second copy of every attribute list, 6,068,892 times on GENCODE: 20.4 s in the comprehension alone, and 13.5% of the whole parse stage.Nothing else can reach this method.
parser.pycalls it only whenself._nativeis true; the pure-Python fallback constructsParsedFeaturedirectly with values it has already normalized. The coercions were defending against a caller that does not exist.
Parsing¶
- gffbase.parser.parse_gff(path: str, *, checklines: int = 10, force_dialect_check: bool = False, force_gff: bool = False, strict: bool = True, validation: str = 'ncbi', engine: str | None = 'auto') _Iterator[source]¶
Parse a GFF3/GTF file (plain text or
.gz).Returns an iterator of
ParsedFeatureplus.dialect(),.directives()and.warningsaccessors.- Parameters:
validation ({"ncbi", "gffutils"}) – Which rule set to apply.
"ncbi"(default here) is the full GFF3 specification."gffutils"is the compatibility profile used bycreate_db: every rule still runs, but a violation annotates the record instead of rejecting it, because real annotation files break the spec routinely and gffutils reads them anyway.strict (bool) – What a rejection does. True (default) raises
GFFFormatErroron the first offending line; False skips it and records it initerator.warnings. Undervalidation="gffutils"nothing is rejected, so this only affects lines that cannot be parsed at all.
- gffbase.parser.parse_bytes(data: bytes, *, checklines: int = 10, force_dialect_check: bool = False, force_gff: bool = False, strict: bool = True, validation: str = 'ncbi', engine: str | None = 'auto') _Iterator[source]¶
Reading and writing¶
- class gffbase.iterators.DataIterator(data, checklines: int = 10, transform=None, force_dialect_check: bool = False, from_string: bool = False, **kwargs)[source]¶
Bases:
Legacy factory. Returns an iterator yielding
Feature.Dispatches on the input, the way the subclasses below have always described and the way
gffutils.DataIteratorbehaves:from_string=True–datais the GFF text itself.a URL – fetched to a temporary file first (
_UrlIterator).any other path-like – read from disk, gzipped or not (
_FileIterator).an iterable of
Feature/ParsedFeature– yielded straight back (_FeatureIterator), so a generator can be piped intocreate_dborFeatureDB.updatewithout being written to a file first.
The dispatch was missing: every input went to
_DataIterator, which hands whatever it gets toparse_gff(path). So a URL was opened as a filename and an in-memory feature list raised, while the subclasses that exist to handle both sat unreachable and their docstrings described a behaviour the factory did not have.
- class gffbase.gffwriter.GFFWriter(out: str | PathLike | IOBase, with_header: bool = True, in_place: bool = False)[source]¶
Bases:
objectWrite Feature records back to a GFF/GTF file.
- write_rec(rec: Feature | str) None[source]¶
Write one record, followed by a newline.
- Parameters:
rec – A
Feature, or a pre-formatted GFF line as a string. A trailing newline on a string is not doubled.
- write_recs(recs: Iterable) None[source]¶
Write many records, in the order given.
- Parameters:
recs – An iterable of
Featureobjects or GFF line strings.
- write_gene_recs(db: FeatureDB, gene_id: str | Feature) None[source]¶
Write a gene and its ENTIRE subtree, sorted by start.
- Parameters:
db – The
FeatureDBto read from.gene_id – The gene, as an id or a
Feature.
- write_mRNA_children(db: FeatureDB, mrna_id: str | Feature) None[source]¶
Write a transcript and its DIRECT children, sorted by start.
- Parameters:
db – The
FeatureDBto read from.mrna_id – The transcript, as an id or a
Feature.
- write_exon_children(db: FeatureDB, exon_id: str | Feature) None[source]¶
Write an exon and its direct children, sorted by start.
- Parameters:
db – The
FeatureDBto read from.exon_id – The exon, as an id or a
Feature.
- close() None[source]¶
Flush, and close only a handle this writer opened.
A stream the caller passed in belongs to the caller. Closing it – as this used to, and as gffutils still does – means
GFFWriter(sys.stdout)shuts stdout down for the whole process, so anything written afterwards raisesValueError: I/O operation on closed file. It is flushed instead, which is the part that actually matters for the output being complete.
- gffbase.sqlite_export.export_sqlite(con: DuckDBPyConnection, path: str, force: bool = False) str[source]¶
Write a legacy SQLite
.dbfrom the given DuckDB connection.The result is openable by real gffutils. Because gffutils has no way to represent a discontinuous feature, one is flattened back into the N features gffutils itself would have made – see
_legacy_ids. The grouping is not lost:duplicatesrecords(logical_id, legacy_id)for every segment past the first, which is both what that table means and how a re-import can rediscover it.Returns the absolute path on success.
Merge criteria¶
Predicates consumed by FeatureDB.merge and merge_all.
Merge predicates — pure functions consumed by FeatureDB.merge.
Signature: (acc: Feature, cur: Feature, components: list[Feature]) -> bool.
acc is the running accumulator; cur is the candidate to fold in;
components is the list of already-folded features. All callables return
True if the candidate should be merged into the accumulator.
Mirrors the legacy gffutils.merge_criteria module.
- gffbase.merge_criteria.seqid(acc, cur, components)[source]¶
Same sequence. Almost always wanted – omitting it merges features on different chromosomes into one.
- gffbase.merge_criteria.strand(acc, cur, components)[source]¶
Same orientation. Leave this out to merge regardless of strand.
- gffbase.merge_criteria.feature_type(acc, cur, components)[source]¶
Same featuretype, so exons do not merge with CDSs.
- gffbase.merge_criteria.exact_coordinates_only(acc, cur, components)[source]¶
Identical span. Merges duplicates, nothing else.
- gffbase.merge_criteria.overlap_end_inclusive(acc, cur, components)[source]¶
curstarts withinacc, or immediately after it.“Immediately after” is the
+ 1: two features that abut with no gap are adjacent, not overlapping, and merging them is normally what a caller wants when collapsing exon runs.
- gffbase.merge_criteria.overlap_start_inclusive(acc, cur, components)[source]¶
curends withinacc, or immediately before it.
- gffbase.merge_criteria.overlap_end_threshold(threshold: int)[source]¶
curstarts within the accumulator, allowing a gap ofthreshold.Changed in 0.2.0. This and the two factories below are RANGE tests, not distance tests. They used to compute
abs(acc.end - cur.start) <= threshold, which reads naturally but answers a different question: it asks how far apart two boundaries are, and so rejects a feature lying entirely inside the accumulator – the most unambiguous overlap there is.Concretely, with
acc = (1, 100),cur = (50, 200),threshold = 5, the old form gaveabs(100 - 50) = 50 <= 5-> False, and these two plainly overlapping features did not merge. This form gives1 <= 50 <= 105-> True.If you call
mergeormerge_allwith one of these, the set of features that merge has changed. Nothing else in the merge machinery did.
Validation and migration¶
Post-ingest invariants.
Schema v2 spreads one logical feature across two tables, and the interesting
failure mode is not a crash – it is a database that answers every query
plausibly and wrongly. The worst case is INV-5: if a fused feature’s envelope
is narrower than its segments, region() silently stops returning it. Nothing
raises, no count looks odd, and the feature is simply gone from results.
So these run automatically at the end of a strict-mode ingest, and can be run by hand at any time. Every check is a single set-based query returning a count plus a few examples, so validating a GENCODE-scale database is a handful of aggregate scans rather than a row-by-row walk.
Severity is not decoration. An error means the database will give wrong
answers; a warning means something is unusual but defensible in real data –
abutting segments, for instance, occur in files people actually ship.
- gffbase.validate.LEVELS = ('fast', 'full')¶
fulladditionally re-parses stored attribute blobs, which is the only check that scales with feature count rather than with table count.
- class gffbase.validate.Violation(invariant: str, name: str, severity: str, count: int, detail: str, examples: tuple = ())[source]¶
Bases:
objectOne invariant that did not hold.
- class gffbase.validate.ValidationReport(level: str, checked: list[str] = <factory>, checked_ids: list[str] = <factory>, violations: list[Violation] = <factory>, skipped: list[str] = <factory>, attribute_eligible: int | None = None, attribute_checked: int | None = None)[source]¶
Bases:
objectWhat validation found. Falsy when anything failed at
errorseverity.- attribute_eligible: int | None = None¶
INV-12 candidates with a stored, non-synthetic attribute blob.
- exception gffbase.validate.ValidationError[source]¶
Bases:
AssertionErrorA database violates an invariant that makes its answers wrong.
AssertionErrorrather thanValueError: this is never bad input from the caller, it is gffbase having produced or been handed a database whose internal structure contradicts itself.
- gffbase.validate.validate_db(db, level: str = 'fast', *, raise_on_error: bool = False, sample: int | None = 200) ValidationReport[source]¶
Check a database’s structural invariants.
level="fast"runs every check that is a fixed number of aggregate scans.level="full"adds INV-12, which re-parses stored attribute blobs forsamplefeatures and is the only check whose cost grows with the corpus.Checks that depend on structures a database does not have – a v1 shim has no
segments– are recorded as skipped rather than silently passing, so a green report cannot mean “nothing was looked at”.
Upgrading a schema v1 database in place.
Two separate operations, deliberately not combined:
migrate_v1_to_v2()Structural only –
ADD COLUMN,CREATE TABLE,CREATE VIEW. It must never change what any query returns, so it does not attempt to recognise v1’sx_1/x_2rows as segments of one discontinuous feature. An in-place upgrade that silently merged 18 features into 17 would be a data change wearing a migration’s clothes.coalesce_multipart()The opt-in second step that does exactly that, reusing the ingest resolve pass. It changes results, which is why the caller has to ask for it.
What the migration deliberately does NOT do is backfill raw_id. NULL there
means “this database predates the column, and the id column 9 originally
carried is unknown” – which is the truth. Setting raw_id = id would be a
lie precisely for the rows that matter: the ones create_unique renamed.
meta.raw_id_valid records it, and the resolve pass skips NULL raw_id, so a
migrated database is simply inert for fusion until coalesce_multipart runs.
- class gffbase.migrate.MigrationResult(from_version: str, to_version: str, changed: bool = False, applied: list[str] = <factory>)[source]¶
Bases:
objectWhat a migration did, for logging and for tests to assert on.
- gffbase.migrate.migrate_v1_to_v2(target, *, con: DuckDBPyConnection | None = None) MigrationResult[source]¶
Upgrade a schema v1 database to v2, in place.
targetmay be a path or an open connection. The whole upgrade runs in one transaction, so a database is never left half-migrated; and it is idempotent, so running it against a database that is already v2 is a no-op that reportschanged=Falserather than an error.Every statement is additive. No feature row’s data is altered, and a caller’s queries return exactly what they returned before – which is the property that makes this safe to run automatically at open.
- gffbase.migrate.coalesce_multipart(db, *, on_multipart_conflict: str = 'error') int[source]¶
Re-fuse rows a v1
create_uniquesplit apart. Returns the count fused.Deliberately NOT part of
migrate_v1_to_v2(): it changes query results, merging what were N features into one, and an in-place upgrade must never do that behind a caller’s back.Reuses the ingest resolve pass, so the GFF3 predicate applies unchanged – segments must share seqid, source, featuretype and strand, and a run that does not is reported rather than fused.
Exceptions¶
Exception classes — verbatim port of legacy gffutils.exceptions.
Same names, same constructors, same attributes. Downstream code that catches
gffutils.FeatureNotFoundError works against gffbase.FeatureNotFoundError.
- exception gffbase.exceptions.GFFFormatError(message: str = '', *, line_no: int = 0, kind: str = '')[source]¶
Bases:
ValueErrorRaised when a GFF3 line violates the spec.
Inherits from
ValueErrorso legacy code that catchesValueErrorkeeps working — but the dedicated subclass carries structured fieldsline_no,kind, andmessagethat point callers straight to the offender in the input.When the Rust extension is loaded, the canonical class is
gffbase._native.GFFFormatError(a PyO3create_exception!type). At import time,gffbase.__init__rebinds the public name to whichever class is actually live soisinstanceandexceptclauses keep working regardless of which path raised.
- exception gffbase.exceptions.FeatureNotFoundError(feature_id: str)[source]¶
Bases:
ExceptionRaised by
FeatureDB.__getitem__when the requested ID is absent.
- exception gffbase.exceptions.DuplicateIDError[source]¶
Bases:
ValueErrorTwo features resolved to the same primary key.
Raised during ingestion under
merge_strategy="error"(the default).
- exception gffbase.exceptions.SynthesisConflictError[source]¶
Bases:
DuplicateIDErrorOne inferred GTF parent ID spans incompatible genomic groups.
A gene or transcript cannot be synthesized safely when the same raw
gene_idortranscript_idoccurs on more than one(seqid, strand)pair. The default is to raise rather than silently choose one location and build an envelope across chromosomes. Callers that deliberately want one parent per group can opt intomerge_strategy="create_unique".Subclassing
DuplicateIDErrorpreserves compatibility with code that already catches duplicate identifiers during ingestion.
- exception gffbase.exceptions.AttributeStringError[source]¶
Bases:
ValueErrorRaised on malformed col-9 attributes.
- exception gffbase.exceptions.EmptyInputError[source]¶
Bases:
ValueErrorRaised when an input file or iterable yields no features.
- exception gffbase.exceptions.SchemaVersionError[source]¶
Bases:
ValueErrorThe database’s schema version is one this build cannot read.
Raised when opening a database written by a newer gffbase, or one whose
meta.schema_versionis unintelligible. An older version is not an error: it degrades to a read-only compatibility mode instead.This exists because the version was previously written and never read, so a schema mismatch surfaced as a missing-column error from whichever query happened to run first – or, worse, as a silently wrong answer from one that did not touch the new columns.
- exception gffbase.exceptions.MultipartConstraintError[source]¶
Bases:
ValueErrorLines sharing one
IDcannot be one discontinuous feature.GFF3 requires the segments of a discontinuous feature to agree on seqid, source, featuretype and strand. Raised in
mode="strict"when they do not; passon_multipart_conflict="split"to partition them instead.
- exception gffbase.exceptions.ReadOnlyError[source]¶
Bases:
ValueErrorA write was attempted on a database opened with
read_only=True.
- exception gffbase.exceptions.ClosedDatabaseError[source]¶
Bases:
ValueErrorA
FeatureDBwas used afterclose().Raised in place of DuckDB’s own
ConnectionException, which reports that a connection is closed without saying which object or which call.
Compatibility modules¶
Ported surface-for-surface from gffutils so that a script that imported
them keeps working. See Migrating from gffutils to gffbase for what is guaranteed.
Compatibility helpers.
- gffbase.helpers.merge_attributes(attr1, attr2, numeric_sort: bool = False) dict[source]¶
Union two attribute mappings, key by key.
Values are deduplicated and sorted, not kept in insertion order: the result is a set union with a deterministic rendering, which is what makes two features merge to the same attributes regardless of which was seen first.
numeric_sortsorts numerically where every value of a key parses as a number, so["2", "10"]does not come back as["10", "2"].Used by every derived-feature method; ported from
gffutils.helpersso that a caller comparing derived output against the oracle sees the same ordering.
- gffbase.helpers.example_filename(fn: str) str[source]¶
Return the absolute path to a packaged example file under
tests/data.Mirrors the legacy
gffutils.example_filenameAPI. The legacy package shipped fixtures inside the package itself; we point at the test fixtures directory bundled with the source distribution.
- gffbase.helpers.HERE = '/home/runner/work/gffbase/gffbase/python/gffbase'¶
Directory this package lives in. Upstream locates bundled example data relative to it;
example_filenamebelow does the same, and this is exported because callers use it to find their own files next to the package.
- gffbase.helpers.infer_dialect(attributes: str) dict[source]¶
Infer a dialect from one attribute string.
Returns the full dialect dict, with everything the single string does not speak to left at its default.
- gffbase.helpers.dialect_compare(dialect1: dict, dialect2: dict) dict[source]¶
What changed between two dialects, as
{"added": ..., "removed": ...}.Unhashable values (
orderis a list) are compared by key rather than through a set of items, so this works on real dialects. The oracle’s version raisesTypeError: unhashable type: 'list'on any dialect that carries anorder, which is all of them.
- gffbase.helpers.to_unicode(obj, encoding: str = 'utf-8')[source]¶
Decode bytes to
str; pass anything else through.A Python-2-era shim. Upstream’s body is unreachable after
2to3and is the identity for every input, including bytes; this one actually decodes, because returning bytes from a function namedto_unicodeis not useful to anybody.
- gffbase.helpers.is_gff_db(db_fname) bool[source]¶
True if the path looks like an existing database file.
Extension-based, as upstream:
.dbfor a gffutils database, plus.duckdb, which is what gffbase writes.
- gffbase.helpers.get_gff_db(gff_fname, ext: str = '.db')[source]¶
Open the database beside a GFF file, or build one.
Always returns a
FeatureDB. Upstream returns a path string when a sibling database exists and aFeatureDBwhen it had to build one, which is whygffutils-cli fetchraisesTypeError: string indices must be integersin its common case – it indexes whatever it gets. Returning one type is the fix.The sibling filename is
<gff_fname><ext>; upstream’s"%s.%s"inserts a second dot, so it looks forfoo.gff..db.
- gffbase.helpers.sanitize_gff_db(db, gid_field: str = 'gid')[source]¶
Return a copy of
dbwith coordinates ordered and a gene id stamped on.Two repairs, both aimed at making a file greppable and self-consistent: every record gets
start <= end, and every record inherits its gene’s id undergid_field, so a gene’s whole block can be found with onegrep.
- gffbase.helpers.sanitize_gff_file(gff_fname, in_memory: bool = True, in_place: bool = False) None[source]¶
Sanitize a GFF file, writing to stdout or over the input.
- gffbase.helpers.annotate_gff_db(db)[source]¶
Cross-reference a GFF database against another.
Not implemented, and not implemented upstream either – gffutils’ body is a bare
pass, so it silently returns None. The name exists so an import resolves; calling it raises rather than pretending to work, because a function that quietly does nothing is worse than one that says so.
- gffbase.helpers.canonical_transcripts(db, fasta_filename)[source]¶
Yield
(transcript, sequence)for the canonical transcript of each gene.Canonical means the longest CDS, falling back to the longest transcript when a gene has no CDS at all.
Two upstream defects are not reproduced: its fallback sorts ascending and takes
[0], selecting the shortest transcript against its own comment; and it callsprint()on an internal tuple for every gene, which corrupts any piped output.
- gffbase.helpers.make_query(args, other=None, limit=None, strand=None, featuretype=None, extra=None, order_by=None, reverse=False, completely_within=False)[source]¶
Compose a legacy SQLite query and its arguments.
Provided for code that builds queries against an exported database, or that reads gffutils’ generated SQL. gffbase’s own queries do not go through it – they are built in
gffbase.interfaceagainst the DuckDB schema.One deviation, and it is the point of having this here:
order_byis validated even when it is a plain string. Upstream checks its whitelist only for the iterable form and interpolates a bare string verbatim, which is the same class of hole thatFeatureDB.order_byhad. Seedocs/source/content/advisory_sql_injection.rst.Danger
otherandextraare raw SQLfeaturetype,limitandstrandbecome bound parameters, andorder_byis checked against a whitelist – butotherandextraare interpolated verbatim, because they exist to carry a caller’s own SQL fragment (upstream builds its relation joins throughother). Passing untrusted input to either is equivalent to passing it toexecute(). No gffbase code path routes caller data into them; the asymmetry is documented here because the surrounding validation makes it easy to assume otherwise.
Summarise a GFF/GTF source without building a database.
The point is to answer “what featuretypes are in this file?” before deciding what to keep, on a file too large to want to ingest twice.
- gffbase.inspect.inspect(data, look_for=['featuretype', 'chrom', 'attribute_keys', 'feature_count'], limit=None, verbose: bool = True) dict[source]¶
Count things in a GFF/GTF source.
- Parameters:
data – A filename, a
FeatureDB(itsall_features()is used), or any iterable of features.look_for (list) – What to tally. Any
Featureattribute name works (chrom,source,strand, …), plus the special"attribute_keys", which counts column-9 keys rather than a field."feature_count"is always reported whether or not it is requested.limit (int) – Stop after this many features.
verbose (bool) – Report progress to stderr.
- Returns:
dict – One key per entry in
look_for, each mapping value -> count, plusfeature_count.The mutable default for
look_foris upstream’s and is part of the pinnedsignature. It is never mutated here, which is what makes it harmless.
Conversions that operate on a FeatureDB.
- gffbase.convert.to_bed12(f, db, child_type: str = 'exon', name_field: str = 'ID') str[source]¶
Build one BED12 line for a top-level feature.
Superseded by
FeatureDB.bed12, which this delegates to so the two cannot disagree, but still importable because gffutils exports it.Two differences from
FeatureDB.bed12are upstream’s, not ours, and are preserved: the line ends with a newline, and the thick span always covers the whole feature (this function predates thick/thin handling and never looks at CDS children).
UCSC genomic binning, the public surface.
gffbase._bins has the arithmetic and is what the SQLite export uses; this
module is the compatibility face of it, under the names gffutils.bins
exports. The two must not diverge, so everything here delegates rather than
reimplements — tests/test_compat_surface.py pins that.
The one thing this adds over _bins is one=False: the set of bins a range
overlaps, rather than the single smallest bin containing it. That is what a
query needs (WHERE bin IN (...)) as opposed to what a write needs, and it is
required by helpers.make_query.
- gffbase.bins.COORD_OFFSETS = {'bed': 0, 'gff': 1}¶
How much to subtract from
startto reach 0-based coordinates. GFF is 1-based and BED is 0-based, and getting this wrong shifts every bin at a level boundary.
- gffbase.bins.FIRST_SHIFT = 17¶
The finest bin is 2**17 wide.
- gffbase.bins.NEXT_SHIFT = 3¶
each level’s bins are 8x the width of the one below.
- Type:
Shift per level
- gffbase.bins.OFFSETS = [4681, 585, 73, 9, 1]¶
Bin number at the start of each level, finest first.
- gffbase.bins.bins(start: int, stop: int, fmt: str = 'gff', one: bool = True)[source]¶
The UCSC bin(s) for the range
[start, stop].- Parameters:
start – Range endpoints, inclusive.
stop – Range endpoints, inclusive.
fmt ({"gff", "bed"}) – Coordinate convention of
start; seeCOORD_OFFSETS.one (bool) – True (default) returns the single smallest bin that fully contains the range – what you store on a row. False returns the
setof every bin the range overlaps at any level – what you query with, since a feature in a coarser bin can still overlap your range.MAX_CHROM_SIZE (Ranges at or beyond)
ones (and negative)
1 (collapse to bin)
chromosome") (("somewhere on this)
rather (which is how the scheme degrades)
represent. (than raising on a coordinate it cannot)
- gffbase.bins.print_bin_sizes() None[source]¶
Report each level’s bin count and width. A debugging aid, kept because the oracle exports it and upstream examples call it.
→ Usage Gallery — every public method, copy-pasteable for worked examples of every method · Migrating from gffutils to gffbase for the gffutils mapping