Skip to content
Merged

Dev #103

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

strategy:
matrix:
os: [macos-13, ubuntu-latest]
os: [macos-latest, ubuntu-latest]
python-version: ["3.9"]

steps:
Expand All @@ -34,11 +34,10 @@ jobs:
- name: Install project
shell: bash -l {0}
run: |
conda install python=${{ matrix.python-version }}
conda install python=${{ matrix.python-version }} pip
python -m pip install --upgrade pip
pip install -e .
pip install black
pip install isort
pip install ruff
pip install pytest
pip install pytest-cov
- name: Check formatting
Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: conda-incubator/setup-miniconda@v3
with:
python-version: 3.9
Expand All @@ -26,10 +26,7 @@ jobs:
run: |
python -m pip install -U pip
pip install -e .
pip install black
pip install isort
pip install pytest
pip install pytest-cov
pip install ruff
- name: Build a binary wheel and a source tarball
shell: bash -l {0}
run: just build
Expand Down
7 changes: 7 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# History

# 1.4.0

* Fixes [#102](https://github.com/gbouras13/dnaapler/issues/102). `dnaapler largest`, `mystery` and `nearest` could reorient to the wrong coordinate when the chosen CDS was on the negative strand, as the lowest coordinate of the CDS was used instead of its actual start codon. The reoriented sequence now correctly begins at the start codon. A related latent bug in the negative-strand fallback of the MMseqs2-based reorientation was also fixed.
* If you run `dnaapler` with GFA input, it now also writes a `{prefix}_reoriented.fasta` alongside the `{prefix}_reoriented.gfa`. This FASTA contains all contigs from the GFA (circular contigs reoriented with overlap trimmed, non-circular contigs passed through unchanged), so it can be fed directly to downstream tools such as polishers.
* If a GFA input contains no circular sequences, `dnaapler` no longer exits with an error. Instead it warns, copies the input GFA to the output, and writes all sequences out as a linear `{prefix}_reoriented.fasta`. For `dnaapler all` and `dnaapler bulk`, a reorientation summary is also written with every contig marked as not reoriented.
* Internal: migrated linting and formatting to [ruff](https://docs.astral.sh/ruff/), added golden-output regression tests, and fixed the CI workflows.

# 1.3.0 (2025-08-21)

* Thanks @mbhall88 for extending the functionality of `--ignore` and generally cleaning up the codebase a bit
Expand Down
4 changes: 3 additions & 1 deletion docs/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

dnaapler creates a number of output files. For all subcommands that are not `dnaapler bulk`, inside the output directory you will find:

* A `{prefix}_reoriented.fasta` containing the reoriented genome. If the input file was in GFA format, this will instead be `{prefix}_reoriented.gfa`.
* A `{prefix}_reoriented.fasta` containing the reoriented genome. If the input file was in GFA format, dnaapler will additionally write a `{prefix}_reoriented.gfa`. In this case the `{prefix}_reoriented.fasta` contains all contigs from the GFA — circular contigs reoriented (with overlap trimmed), and non-circular contigs passed through unchanged — so it can be fed directly to downstream tools such as polishers. Reoriented contigs are indicated in the FASTA header with `rotated=True`.

* If the input GFA contains no circular sequences, there is nothing to reorient. Rather than exiting with an error, dnaapler will warn, copy the input GFA to `{prefix}_reoriented.gfa`, and write all sequences out as a linear `{prefix}_reoriented.fasta` unchanged. For `dnaapler all` and `dnaapler bulk`, a reorientation summary is also written (`{prefix}_all_reorientation_summary.tsv` / `{prefix}_bulk_reorientation_summary.tsv`) with every contig marked `No_reorientation`.

* A `{prefix}_blast_output.txt` if a BLAST based method is used. This file will contain the raw blastx results in BLAST output format 6.

Expand Down
16 changes: 6 additions & 10 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ PROJECT := "dnaapler"
OPEN := if os() == "macos" { "open" } else { "xdg-open" }
VERSION := `poetry version | rg -o '\d+\.\d+\.\d+'`

# format code with black and isort
# format code with ruff
fmt:
poetry run black .
poetry run isort .
poetry run ruff format .
poetry run ruff check --fix .

# check format of code with black and isort
# check formatting and lint with ruff
check-fmt:
poetry run black --check .
poetry run isort --check .

# lint code with flake8
lint:
poetry run flake8 .
poetry run ruff format --check .
poetry run ruff check .

# install latest version with poetry
install:
Expand Down
15 changes: 9 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "dnaapler"
version = "1.3.0" # change VERSION too
version = "1.4.0" # change VERSION too
description = "Reorients assembled microbial sequences"
authors = ["George Bouras <george.bouras@adelaide.edu.au>"]
license = "MIT"
Expand All @@ -26,11 +26,9 @@ pyrodigal = ">=3.0.0"


[tool.poetry.dev-dependencies]
black = ">=22.3.0"
isort = ">=5.10.1"
ruff = ">=0.4.0"
pytest = ">=6.2.5"
pytest-cov = ">=3.0.0"
flake8 = ">=3.0.1"

[[tool.poetry.source]]
name = "pypi-test"
Expand All @@ -41,5 +39,10 @@ priority = "primary"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.isort]
profile = "black"
[tool.ruff]
line-length = 88
exclude = ["*.ipynb"]

[tool.ruff.lint]
select = ["E", "F", "W", "I"]
ignore = ["E501"]
49 changes: 40 additions & 9 deletions src/dnaapler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ def chromosome(

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -270,7 +273,10 @@ def archaea(

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -351,7 +357,10 @@ def plasmid(

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -432,7 +441,10 @@ def phage(

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -520,7 +532,10 @@ def custom(

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -645,7 +660,10 @@ def nearest(ctx, input, output, threads, prefix, force, **kwargs):

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# run the nearest workflow
run_nearest(ctx, input, output, prefix)
Expand Down Expand Up @@ -691,7 +709,10 @@ def largest(ctx, input, output, threads, prefix, force, **kwargs):

# validates fasta or gfa
validate_input(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(input, output, prefix)
if gfa_no_circular:
end_dnaapler(start_time)
return

# run the nearest workflow
run_largest(ctx, input, output, prefix)
Expand Down Expand Up @@ -781,7 +802,12 @@ def bulk(
# validates fasta or gfa
validate_input_bulk(input)
check_duplicate_headers(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(
input, output, prefix, summary_type="bulk"
)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down Expand Up @@ -954,7 +980,12 @@ def all(
# validates fasta or gfa
validate_input_all(input)
check_duplicate_headers(input)
input_is_gfa, input, original_gfa = prep_gfa(input, output)
input_is_gfa, input, original_gfa, gfa_no_circular = prep_gfa(
input, output, prefix, summary_type="all"
)
if gfa_no_circular:
end_dnaapler(start_time)
return

# validate E-value
check_evalue(evalue)
Expand Down
15 changes: 12 additions & 3 deletions src/dnaapler/utils/cds_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ def run_mystery(ctx, input: Path, seed_value: int, output: Path, prefix: str) ->
reorient_gene_number = random.randint(2, gene_count - 1)

logger.info(f"Gene number {reorient_gene_number} was selected.")
start = genes[reorient_gene_number].begin
strand = genes[reorient_gene_number].strand
if strand == 1:
start = genes[reorient_gene_number].begin
else:
start = genes[reorient_gene_number].end

if strand == 1:
strand_eng = "forward"
Expand Down Expand Up @@ -112,8 +115,11 @@ def run_nearest(ctx, input: Path, output: Path, prefix: str) -> None:

reorient_gene_number = 1

start = genes[reorient_gene_number].begin
strand = genes[reorient_gene_number].strand
if strand == 1:
start = genes[reorient_gene_number].begin
else:
start = genes[reorient_gene_number].end

if strand == 1:
strand_eng = "forward"
Expand Down Expand Up @@ -176,8 +182,11 @@ def run_largest(ctx, input: Path, output: Path, prefix: str) -> None:
# Find the gene with the max overlap
largest_gene_index = max(size_dict, key=lambda key: size_dict[key])

start = genes[largest_gene_index].begin
strand = genes[largest_gene_index].strand
if strand == 1:
start = genes[largest_gene_index].begin
else:
start = genes[largest_gene_index].end
max_size = size_dict[largest_gene_index] / 3

if strand == 1:
Expand Down
Loading
Loading