Skip to content
Open
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
27 changes: 7 additions & 20 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This repo uses automation agents (local or CI) to keep code healthy and consiste
- **Package management**: use `pipenv` for environment and dependency management, but maintain `pyproject.toml` (preferred) and keep `setup.py` / `setup.cfg` in sync if present.
- **Python**: runtime support starts at **Python 3.11**; develop and run CI on **Python 3.13**.
- **Static checks**: enforce `mypy` and `flake8` on all tracked Python files.
- **Tests**: run `pytest` with `pytest-cov`; fail if coverage drops below the configured threshold.
- **Tests**: run `pytest tests/unit --cov=postalign` and `behave tests/component` (which downloads minimap2 2.17); fail if coverage drops below the configured threshold.
- **Mocks**: use `unittest.mock`; avoid `monkeypatch` or plain stubs.
- **Coverage pragmas**: annotate unavoidable no-op statements with
`# pragma: no cover` and a brief justification. File-wide pragmas are not
Expand Down Expand Up @@ -41,7 +41,8 @@ pipenv run flake8 .
pipenv run mypy .

# Tests + coverage
pipenv run pytest --cov=postalign --cov-report=term-missing
pipenv run pytest tests/unit --cov=postalign --cov-report=term-missing
pipenv run behave tests/component

# Update Pipfile.lock
pipenv lock --dev --clear
Expand All @@ -53,10 +54,10 @@ make requirements.txt
## CI expectations (example)
- Use Python 3.13 runner (project supports Python 3.11+).
- Steps:
1. `pip install -e .[dev]`
2. `flake8 .`
3. `mypy .`
4. `pytest --cov=postalign --cov-report=xml` (record artifact, enforce threshold)
1. `pipenv run pip install -e .[dev]`
2. `pipenv run flake8 .`
3. `pipenv run mypy .`
4. `pipenv run pytest --cov=postalign --cov-report=xml` (record artifact, enforce threshold)

## Sphinx docstring style (minimal rules)
- Use Sphinx fields: `:param name:`, `:type name:`, `:returns:`, `:rtype:`, `:raises:`.
Expand All @@ -68,24 +69,10 @@ make requirements.txt
# With pipenv
pipenv update # safe minor/patch upgrades per constraints
pipenv update <pkg>

# If using pip/requirements:
pip list --outdated
pip install -U <pkg>
# If using pip-tools:
pip-compile --upgrade
pip-sync
```
- Pin in requirements/lockfile as appropriate.
- Run tests and type checks after any upgrade.

## Pre-commit (recommended)
```bash
pip install pre-commit
pre-commit install
# Example hooks: flake8, mypy (via local hook), trailing-whitespace, end-of-file-fixer
```

## Pull request checklist
- [ ] Code runs on Python 3.11+ (tests executed on Python 3.13).
- [ ] Added/updated tests for all touched code.
Expand Down
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
requirements.txt: Pipfile.lock
@pipenv requirements --from-pipfile > requirements.txt
@sed -i '' '/^-e \.$$/d' requirements.txt
@pipenv requirements --from-pipfile | grep -v '^-i' > requirements.txt

test-unit:
@pytest tests/unit

test-component:
@behave tests/component

test: test-unit test-component

build-docker-builder: requirements.txt
@docker pull ubuntu:18.04
Expand Down Expand Up @@ -33,4 +40,4 @@ dist/postalign_linux-amd64.tar.gz: dist/linux-amd64
dist: dist/postalign_linux-amd64.tar.gz
build: dist

.PHONY: build-docker-builder push-docker-builder
.PHONY: test-unit test-component test build-docker-builder push-docker-builder
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mypy = "*"
flake8 = "*"
pytest = "*"
pytest-cov = "*"
behave = "*"

[packages]
more-itertools = "*"
Expand Down
66 changes: 61 additions & 5 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The typical workflow runs minimap2 to generate alignments and writes the
post‑processed result as JSON:

```bash
pipenv run post-align -i reads.fasta -r ref.fasta -o result.json -f MINIMAP2 \
pipenv run post-align -i reads.fas -r ref.fas -o result.json -f MINIMAP2 \
save-json
```

Expand All @@ -44,7 +44,8 @@ Linting, type checking and tests:
```bash
pipenv run flake8 .
pipenv run mypy .
pipenv run pytest --cov=postalign --cov-report=term-missing
pipenv run pytest tests/unit --cov=postalign --cov-report=term-missing
pipenv run behave tests/component # downloads minimap2 2.17 automatically
```

The project currently relies on a minimal `setup.py` for building Cython
Expand Down
75 changes: 25 additions & 50 deletions postalign/processors/codon_alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,32 +609,18 @@ def parse_gap_placement_score(value: str) -> dict[
return scores


@cython.ccall
@cython.returns(dict)
def gap_placement_score_callback(
ctx: typer.Context,
param: typer.CallbackParam,
value: tuple[str, ...],
) -> dict[int, dict[tuple[int, int], int]]:
"""Parse ``--gap-placement-score`` arguments.

:param ctx: Typer context.
:param param: Callback parameter definition.
:param value: Tuple of score specifications.
:returns: Nested mapping of gap placement scores.
:raises typer.BadParameter: On invalid input value.
"""
if not param.name:
raise typer.BadParameter(
'Internal error (gap_placement_score_callback:1)'
)
try:
result: dict[
int, dict[tuple[int, int], int]
] = parse_gap_placement_score(','.join(value))
return result
except ValueError as exp:
raise typer.BadParameter(str(exp))
_GAP_PLACEMENT_HELP = (
'Bonus (positive number) or penalty (negative number) for gaps '
'appear at certain NA position (relative to the WHOLE ref seq) '
'in the ref seq (ins) or target seq (del). For example, '
'204ins:-5 is a -5 penalty designate to a gap with any size '
'gap in ref seq after NA position 204 (AA position 68). '
'2041/12del:10 is a +10 score for a 12 NAs size '
'(4 codons) gap in target seq at NA position 2041, '
'equivalent to deletion at 681, 682, 683 and 684 AA position. '
'Multiple scores can be delimited by commas, such as '
'204ins:-5,2041/12del:10.'
)


@cli.command('codon-alignment')
Expand Down Expand Up @@ -662,29 +648,12 @@ def codon_alignment(
),
] = 10,
gap_placement_score: Annotated[
# For NASize, 0 means any size
# Indel NAPos NASize Score
# v v v v
dict[int, dict[tuple[int, int], int]],
list[str] | None,
typer.Option(
(), '--gap-placement-score',
callback=gap_placement_score_callback,
multiple=True,
help=(
'Bonus (positive number) or penalty (negative number) for gaps'
' appear at certain NA position '
'(relative to the WHOLE ref seq) '
'in the ref seq (ins) or target seq (del). For example, '
'204ins:-5 is a -5 penalty designate to a gap with any size '
'gap in ref seq after NA position 204 (AA position 68). '
'2041/12del:10 is a +10 score for a 12 NAs size'
' (4 codons) gap in target seq at NA position 2041,'
' equivalent to deletion at 681, 682, 683 and 684 AA position.'
' Multiple scores can be delimited by commas, such as '
'204ins:-5,2041/12del:10.'
),
), # type: ignore[call-overload]
] = {},
None, '--gap-placement-score',
help=_GAP_PLACEMENT_HELP,
),
] = None,
ref_start: Annotated[int, typer.Argument()] = 1,
ref_end: Annotated[int, typer.Argument()] = -1,
# XXX: see https://github.com/cython/cython/issues/2753
Expand All @@ -699,11 +668,17 @@ def codon_alignment(
:param min_gap_distance: Minimal nucleotide gap distance of the output.
:param window_size: Amino acid window size for finding optimal placement.
:param gap_placement_score: Bonus or penalty scores for gaps at specific
positions.
positions, expressed as repeated ``--gap-placement-score`` options in
the form ``<pos>[/<size>](ins|del):<score>``.
:param ref_start: Start position relative to reference sequence.
:param ref_end: End position relative to reference sequence.
:raises typer.BadParameter: If provided positions are invalid.
"""
if gap_placement_score:
gap_scores = parse_gap_placement_score(','.join(gap_placement_score))
else:
gap_scores = {REFGAP: {}, SEQGAP: {}}

if ref_start < 1:
raise typer.BadParameter(
f'argument <REF_START>:{ref_start} must be not less than 1'
Expand Down Expand Up @@ -740,7 +715,7 @@ def processor(
seq,
min_gap_distance,
window_size,
gap_placement_score,
gap_scores,
ref_start, my_ref_end
)

Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
-i https://pypi.org/simple
cython==3.1.2; python_version >= '3.8'
more-itertools==10.7.0; python_version >= '3.9'
orjson==3.11.1; python_version >= '3.9'
pafpy==0.2.0; python_version >= '3.6' and python_version < '4.0'
rich==14.1.0; python_full_version >= '3.8.0'
typer==0.16.0; python_version >= '3.7'
types-setuptools==80.9.0.20250809; python_version >= '3.9'
rich==14.1.0; python_version >= '3.9'
typer==0.16.0; python_version >= '3.9'
Loading