diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml new file mode 100644 index 0000000..f8cf627 --- /dev/null +++ b/.github/workflows/build-check.yml @@ -0,0 +1,39 @@ +name: Build check + +on: + pull_request: + branches: + - master + paths: + - 'src/**' + - 'pyproject.toml' + - 'setup.cfg' + - 'VERSION.txt' + - 'MANIFEST.in' + - '.github/workflows/build-check.yml' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: '3.12' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build sdist and wheel + run: python -m build + + - name: Check package metadata + run: python -m twine check dist/* diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 449480b..00eba1d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,12 +23,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 9f90823..becf9eb 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -23,9 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout repository' - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@v5.0.0 # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. with: comment-summary-in-pr: always diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 17eaf82..b2f9863 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -18,10 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: '3.12' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 89485b6..a8b22d2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: steps: # Step 1: Checkout the repository - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 # Fetch the full history to allow branch checkout diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb45466..12391fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,9 +13,9 @@ jobs: python-version: ['3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index f9e2b2e..2152f4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * planned new feature: during import of long reads, (optionally) correct for short exon alignment issues. * separate new read import and classification of isoforms. +## [2.1.2] + +* fixed: gff3 import silently dropped genes when the file's chromosome/seqid naming didn't match the genome (e.g. RefSeq-style accessions like `NC_000001.11` vs a plain `1`/`chr1` genome FASTA); chromosome name aliasing via `region` feature lines is now resolved again, restoring behavior lost when the tabix-based reader was replaced to fix #28 (#36) + ## [2.1.1] * fixed: `Transcriptome[...]` gene lookup by name silently returned an arbitrary gene when the name was shared by multiple genes (common for duplicated gene symbols); now warns clearly, separately from the existing gene id ambiguity check (which itself had a bug: it checked against the combined id+name index instead of ids alone) (#27) diff --git a/VERSION.txt b/VERSION.txt index 3e3c2f1..eca07e4 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -2.1.1 +2.1.2 diff --git a/src/isotools/_transcriptome_io.py b/src/isotools/_transcriptome_io.py index ae0e6c9..d1ac86d 100644 --- a/src/isotools/_transcriptome_io.py +++ b/src/isotools/_transcriptome_io.py @@ -1782,6 +1782,12 @@ def _read_gff_file(file_name, chromosomes, infer_genes=False, progress_bar=True) is_gz = file_name.endswith(".gz") openfun = gziplib.open if is_gz else open + # seqid -> chromosome name, learned from "region" feature lines that + # carry a "chromosome" attribute (common in RefSeq-style GFF3, where + # seqid is an accession like NC_000001.11 and "chromosome" gives the + # plain name, e.g. "1") -- restores the chromosome aliasing that the + # old TabixFile-based reader did via get_gff_chrom_dict + chrom_alias = {} with ( openfun(file_name, "rt") as gff, @@ -1808,20 +1814,35 @@ def _read_gff_file(file_name, chromosomes, infer_genes=False, progress_bar=True) logger.warning("GFF line has fewer than 9 fields, skipping:\n%s", line) continue - chrom = ls[0] + raw_chrom = ls[0] + chrom = chrom_alias.get(raw_chrom, raw_chrom) + region_info = None + if ls[2] == "region": + try: + region_info = dict( + [pair.split("=", 1) for pair in ls[8].rstrip(";").split(";")] + ) + except ValueError: + region_info = {} + if "chromosome" in region_info: + chrom = chrom_alias[raw_chrom] = region_info["chromosome"] + if chromosomes is not None and chrom not in chromosomes: logger.debug("skipping line from chr " + chrom) continue - try: - info = dict( - [pair.split("=", 1) for pair in ls[8].rstrip(";").split(";")] - ) # some gff lines end with ';' in gencode 36 - except ValueError: - logger.warning( - "GFF format error in infos (should be ; separated key=value pairs). Skipping line:\n%s", - line, - ) - continue + if region_info is not None: + info = region_info + else: + try: + info = dict( + [pair.split("=", 1) for pair in ls[8].rstrip(";").split(";")] + ) # some gff lines end with ';' in gencode 36 + except ValueError: + logger.warning( + "GFF format error in infos (should be ; separated key=value pairs). Skipping line:\n%s", + line, + ) + continue start, end = [int(i) for i in ls[3:5]] start -= 1 # to make 0 based diff --git a/tests/data/refseq_style_chrom_alias.gff3 b/tests/data/refseq_style_chrom_alias.gff3 new file mode 100644 index 0000000..bc3c698 --- /dev/null +++ b/tests/data/refseq_style_chrom_alias.gff3 @@ -0,0 +1,5 @@ +##gff-version 3 +NC_000001.11 RefSeq region 1 248956422 . + . ID=NC_000001.11:1..248956422;chromosome=1;gbkey=Src +NC_000001.11 BestRefSeq gene 100 500 . + . ID=GENE1 +NC_000001.11 BestRefSeq transcript 100 500 . + . ID=GENE1.1;Parent=GENE1 +NC_000001.11 BestRefSeq exon 100 500 . + . Parent=GENE1.1 diff --git a/tests/data_import_test.py b/tests/data_import_test.py index f34aec5..2611c51 100644 --- a/tests/data_import_test.py +++ b/tests/data_import_test.py @@ -16,6 +16,22 @@ def test_import_gff(): assert True +def test_import_gff_chromosome_alias(): + # regression test for #36: RefSeq-style GFF3 files use an accession as + # seqid (e.g. NC_000001.11) with the plain chromosome name (e.g. "1") + # given via a "chromosome" attribute on a "region" feature line. Genes + # were previously silently dropped when filtering against the plain + # name, since the tabix-based alias resolution was lost when the + # reader was rewritten to fix #28. + transcriptome = Transcriptome.from_reference( + "tests/data/refseq_style_chrom_alias.gff3", chromosomes={"1"} + ) + assert len(transcriptome) == 1, "gene should be found via chromosome alias" + gene = next(iter(transcriptome)) + assert gene.id == "GENE1" + assert gene.chrom == "1", "gene should be indexed under the aliased chromosome name" + + def test_add_sample_from_csv_missing_gene_info(): # regression test for #25: a coverage csv row referencing a transcript_id # not found in the transcripts file previously broke gene_id/chr column