gside 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gside-0.1.1/.gitattributes +2 -0
- gside-0.1.1/.github/workflows/docs.yml +42 -0
- gside-0.1.1/.github/workflows/publish-pypi.yml +64 -0
- gside-0.1.1/.github/workflows/test.yml +56 -0
- gside-0.1.1/.gitignore +29 -0
- gside-0.1.1/AGENTS.md +264 -0
- gside-0.1.1/LICENSE +21 -0
- gside-0.1.1/PKG-INFO +114 -0
- gside-0.1.1/README.md +92 -0
- gside-0.1.1/THIRD_PARTY_NOTICES.md +40 -0
- gside-0.1.1/data/db/D1_marker/marker_rules.yaml +234 -0
- gside-0.1.1/data/db/D1_marker/markers.fasta +1502 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.ndb +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.nhr +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.nin +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.njs +22 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.not +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.nsq +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.ntf +0 -0
- gside-0.1.1/data/db/D1_marker/markers_blastdb.nto +0 -0
- gside-0.1.1/data/db/D2_ani/manifests/metadata.tsv +292 -0
- gside-0.1.1/data/db/D2_ani/manifests/panel_accessions.tsv +1645 -0
- gside-0.1.1/docs/architecture/layers.md +65 -0
- gside-0.1.1/docs/architecture/layers.zh.md +60 -0
- gside-0.1.1/docs/index.md +55 -0
- gside-0.1.1/docs/index.zh.md +51 -0
- gside-0.1.1/docs/installation/databases.md +120 -0
- gside-0.1.1/docs/installation/databases.zh.md +103 -0
- gside-0.1.1/docs/installation/marker-database.md +185 -0
- gside-0.1.1/docs/installation/marker-database.zh.md +177 -0
- gside-0.1.1/docs/installation/quick-start.md +64 -0
- gside-0.1.1/docs/installation/quick-start.zh.md +57 -0
- gside-0.1.1/docs/reference/json-contract.md +162 -0
- gside-0.1.1/docs/reference/json-contract.zh.md +152 -0
- gside-0.1.1/docs/reference/marker-rules.md +121 -0
- gside-0.1.1/docs/reference/marker-rules.zh.md +112 -0
- gside-0.1.1/docs/usage/cli.md +141 -0
- gside-0.1.1/docs/usage/cli.zh.md +135 -0
- gside-0.1.1/docs/usage/modes.md +162 -0
- gside-0.1.1/docs/usage/modes.zh.md +154 -0
- gside-0.1.1/mkdocs.yml +62 -0
- gside-0.1.1/pixi.lock +2806 -0
- gside-0.1.1/pixi.toml +40 -0
- gside-0.1.1/pyproject.toml +54 -0
- gside-0.1.1/src/gside/__init__.py +15 -0
- gside-0.1.1/src/gside/__main__.py +6 -0
- gside-0.1.1/src/gside/analysis/__init__.py +0 -0
- gside-0.1.1/src/gside/analysis/ani_identifier.py +184 -0
- gside-0.1.1/src/gside/analysis/multigene_identifier.py +218 -0
- gside-0.1.1/src/gside/analysis/sourmash_identifier.py +125 -0
- gside-0.1.1/src/gside/analysis/species_canon.py +53 -0
- gside-0.1.1/src/gside/analysis/taxonomic_validator.py +234 -0
- gside-0.1.1/src/gside/cli.py +374 -0
- gside-0.1.1/src/gside/config.py +49 -0
- gside-0.1.1/src/gside/db.py +447 -0
- gside-0.1.1/src/gside/engine/__init__.py +97 -0
- gside-0.1.1/src/gside/engine/_env.py +48 -0
- gside-0.1.1/src/gside/engine/backends/__init__.py +51 -0
- gside-0.1.1/src/gside/engine/backends/blast.py +114 -0
- gside-0.1.1/src/gside/engine/backends/kma.py +141 -0
- gside-0.1.1/src/gside/engine/backends/kmer.py +232 -0
- gside-0.1.1/src/gside/engine/backends/minimap2.py +83 -0
- gside-0.1.1/src/gside/engine/backends/mmseqs2.py +70 -0
- gside-0.1.1/src/gside/engine/backends/skani.py +73 -0
- gside-0.1.1/src/gside/engine/hits.py +116 -0
- gside-0.1.1/src/gside/engine/read_mapper.py +183 -0
- gside-0.1.1/src/gside/engine/registry.py +29 -0
- gside-0.1.1/src/gside/engine/utils.py +82 -0
- gside-0.1.1/src/gside/utils.py +18 -0
- gside-0.1.1/tests/integration/test_agreement.py +42 -0
- gside-0.1.1/tests/integration/test_ani_golden.py +81 -0
- gside-0.1.1/tests/integration/test_backends_golden.py +53 -0
- gside-0.1.1/tests/integration/test_db_build.py +106 -0
- gside-0.1.1/tests/integration/test_marker_golden.py +28 -0
- gside-0.1.1/tests/integration/test_sourmash_golden.py +30 -0
- gside-0.1.1/tests/unit/test_ani_units.py +212 -0
- gside-0.1.1/tests/unit/test_backends_units.py +266 -0
- gside-0.1.1/tests/unit/test_canon_units.py +64 -0
- gside-0.1.1/tests/unit/test_cli.py +127 -0
- gside-0.1.1/tests/unit/test_cli_units.py +483 -0
- gside-0.1.1/tests/unit/test_config_units.py +65 -0
- gside-0.1.1/tests/unit/test_db_units.py +580 -0
- gside-0.1.1/tests/unit/test_docs_units.py +70 -0
- gside-0.1.1/tests/unit/test_engine_core_units.py +162 -0
- gside-0.1.1/tests/unit/test_engine_misc_units.py +115 -0
- gside-0.1.1/tests/unit/test_engine_units.py +212 -0
- gside-0.1.1/tests/unit/test_kmer_minimap_units.py +187 -0
- gside-0.1.1/tests/unit/test_mains_units.py +28 -0
- gside-0.1.1/tests/unit/test_multigene_units.py +241 -0
- gside-0.1.1/tests/unit/test_readmapper_units.py +245 -0
- gside-0.1.1/tests/unit/test_sourmash_units.py +206 -0
- gside-0.1.1/tests/unit/test_taxonomic_units.py +249 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Deploy Docs to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: "pages"
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
build:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v5
|
|
25
|
+
- uses: actions/setup-python@v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
- run: pip install mkdocs mkdocs-material mkdocs-static-i18n
|
|
29
|
+
- run: mkdocs build --strict
|
|
30
|
+
- uses: actions/upload-pages-artifact@v3
|
|
31
|
+
with:
|
|
32
|
+
path: ./site
|
|
33
|
+
|
|
34
|
+
deploy:
|
|
35
|
+
environment:
|
|
36
|
+
name: github-pages
|
|
37
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
needs: build
|
|
40
|
+
steps:
|
|
41
|
+
- id: deployment
|
|
42
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment:
|
|
11
|
+
name: pypi
|
|
12
|
+
url: https://pypi.org/p/gside
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v5
|
|
18
|
+
|
|
19
|
+
- name: Verify version consistency
|
|
20
|
+
run: |
|
|
21
|
+
PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
22
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
23
|
+
echo "pyproject.toml: $PYPROJECT_VERSION"
|
|
24
|
+
echo "git tag: $TAG_VERSION"
|
|
25
|
+
if [ "$PYPROJECT_VERSION" != "$TAG_VERSION" ]; then
|
|
26
|
+
echo "ERROR: pyproject.toml ($PYPROJECT_VERSION) != git tag ($TAG_VERSION)"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
echo "Versions match: $TAG_VERSION"
|
|
30
|
+
|
|
31
|
+
- name: Set up Python
|
|
32
|
+
uses: actions/setup-python@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: '3.12'
|
|
35
|
+
|
|
36
|
+
- name: Install build dependencies
|
|
37
|
+
run: |
|
|
38
|
+
python -m pip install --upgrade pip
|
|
39
|
+
pip install build twine
|
|
40
|
+
|
|
41
|
+
- name: Build package
|
|
42
|
+
run: python -m build
|
|
43
|
+
|
|
44
|
+
- name: Check package
|
|
45
|
+
run: twine check dist/*
|
|
46
|
+
|
|
47
|
+
- name: Verify wheel version and bundled marker tier
|
|
48
|
+
run: |
|
|
49
|
+
python -c "
|
|
50
|
+
import zipfile, glob
|
|
51
|
+
whl = glob.glob('dist/*.whl')[0]
|
|
52
|
+
with zipfile.ZipFile(whl) as z:
|
|
53
|
+
names = z.namelist()
|
|
54
|
+
meta = next(n for n in names if n.endswith('METADATA'))
|
|
55
|
+
version = [l for l in z.read(meta).decode().splitlines() if l.startswith('Version:')][0].split(': ')[1]
|
|
56
|
+
assert version == '${GITHUB_REF_NAME#v}', f'wheel version {version} != tag'
|
|
57
|
+
for asset in ('marker_rules.yaml', 'markers.fasta', 'markers_blastdb.nhr'):
|
|
58
|
+
assert any(n.endswith(f'gside/data/db/D1_marker/{asset}') for n in names), f'{asset} missing from wheel'
|
|
59
|
+
print('wheel OK: version', version, '+ bundled marker tier')
|
|
60
|
+
"
|
|
61
|
+
|
|
62
|
+
- name: Publish to PyPI
|
|
63
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
64
|
+
# No API token needed when using trusted publishing
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
|
|
15
|
+
- name: Set up Python 3.12
|
|
16
|
+
uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Lint with ruff
|
|
26
|
+
run: |
|
|
27
|
+
ruff check src/ tests/
|
|
28
|
+
ruff format --check src/ tests/
|
|
29
|
+
|
|
30
|
+
- name: Type check with pyright
|
|
31
|
+
run: |
|
|
32
|
+
pyright src/gside/
|
|
33
|
+
|
|
34
|
+
- name: Test with pytest (coverage)
|
|
35
|
+
run: |
|
|
36
|
+
pytest -q --cov=src/gside --cov-report=term-missing --cov-fail-under=95
|
|
37
|
+
|
|
38
|
+
pixi:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v5
|
|
43
|
+
|
|
44
|
+
- uses: prefix-dev/setup-pixi@v0.9.5
|
|
45
|
+
|
|
46
|
+
- name: Install pixi environments
|
|
47
|
+
run: pixi install --all
|
|
48
|
+
|
|
49
|
+
- name: Lint and format check
|
|
50
|
+
run: pixi run -e dev check
|
|
51
|
+
|
|
52
|
+
- name: Type check
|
|
53
|
+
run: pixi run -e dev typecheck
|
|
54
|
+
|
|
55
|
+
- name: Run tests
|
|
56
|
+
run: pixi run -e dev pytest tests/ -q --cov=src/gside --cov-report=term-missing
|
gside-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pixi environments
|
|
2
|
+
.pixi/*
|
|
3
|
+
!.pixi/config.toml
|
|
4
|
+
# Large reference databases (not in git — use `gside db setup`)
|
|
5
|
+
# D2_ani contents are ignored except the manifests/ build recipe.
|
|
6
|
+
data/db/D2_ani/*
|
|
7
|
+
!data/db/D2_ani/manifests/
|
|
8
|
+
data/db/D3_mash/
|
|
9
|
+
data/db/D4_sourmash/
|
|
10
|
+
site/
|
|
11
|
+
|
|
12
|
+
# Python
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.py[cod]
|
|
15
|
+
*.egg-info/
|
|
16
|
+
dist/
|
|
17
|
+
build/
|
|
18
|
+
.venv/
|
|
19
|
+
|
|
20
|
+
# Test & lint caches
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
coverage.xml
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
gside-0.1.1/AGENTS.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Agent guidance for this repository. Read this before writing or editing any code.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project Overview
|
|
8
|
+
|
|
9
|
+
- **Language**: Python 3.12 (standalone species-identification CLI, no frontend)
|
|
10
|
+
- **Package manager**: [pixi](https://pixi.sh) — conda tools (blast/skani/mash/mmseqs2) + PyPI editable install via `pixi.toml`
|
|
11
|
+
- **Linter + Formatter**: [ruff](https://docs.astral.sh/ruff/) (config in `pyproject.toml`; run via pip-installed dev extras, no pixi task)
|
|
12
|
+
- **Testing**: pytest (`tests/`; single file `tests/unit/test_cli.py`, needs real `blastn`)
|
|
13
|
+
- **Docs**: MkDocs Material + static-i18n, English default + Chinese secondary (`docs/*.md` + `*.zh.md` pairs)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Environment Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install pixi (if not already installed)
|
|
21
|
+
curl -fsSL https://pixi.sh/install.sh | bash
|
|
22
|
+
|
|
23
|
+
# Install conda tools + editable package
|
|
24
|
+
pixi install
|
|
25
|
+
pip install -e .
|
|
26
|
+
|
|
27
|
+
# Optional docs extras (dev environment)
|
|
28
|
+
pip install -e .[dev]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Bioinformatics binaries come from the pixi `default` env; the Python package is
|
|
32
|
+
pip-installed (editable). `pip install -e .[dev]` adds pytest/ruff/mypy/mkdocs.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Build / Run Commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Species identification (5 modes, default marker)
|
|
40
|
+
pixi run -e dev python -m gside species contigs.fna --mode marker
|
|
41
|
+
pixi run -e dev python -m gside species contigs.fna --mode all
|
|
42
|
+
|
|
43
|
+
# Database management
|
|
44
|
+
pixi run -e dev python -m gside db status
|
|
45
|
+
pixi run -e dev python -m gside db setup --tier panel # panel (default) | mash | mini | all
|
|
46
|
+
|
|
47
|
+
# After pip install -e ., the console script is also available
|
|
48
|
+
gside species contigs.fna --mode panel
|
|
49
|
+
gside --version
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Check `pixi.toml` for the canonical task definitions — only `docs` / `docs-build`
|
|
53
|
+
exist as pixi tasks; everything else runs via `python -m` or the `gside` script.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Lint / Format Commands
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# No pixi tasks exist for lint; use the pip-installed ruff from .[dev]
|
|
61
|
+
python -m ruff check src/ tests/
|
|
62
|
+
python -m ruff format --check src/ tests/
|
|
63
|
+
python -m ruff format src/ tests/
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Config: `pyproject.toml` (`target-version = "py312"`, `line-length = 100`).
|
|
67
|
+
**Always run ruff before committing.** Fix all lint errors; do not use `# noqa`
|
|
68
|
+
suppression unless unavoidable, and always add a comment explaining why.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Testing Commands
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Run all tests (dev env now ships pytest + pytest-cov)
|
|
76
|
+
pixi run -e dev pytest tests/ -q
|
|
77
|
+
|
|
78
|
+
# Run with verbose output
|
|
79
|
+
pixi run -e dev pytest tests/ -v
|
|
80
|
+
|
|
81
|
+
# Unit tests only (pure logic, no binaries/databases — always fast)
|
|
82
|
+
pixi run -e dev pytest tests/unit -q
|
|
83
|
+
|
|
84
|
+
# Integration tests (need blastn/skani/mash + databases)
|
|
85
|
+
pixi run -e dev pytest tests/integration -q
|
|
86
|
+
|
|
87
|
+
# With coverage (ratchet: must not drop below the committed floor)
|
|
88
|
+
pixi run -e dev pytest tests/ -q --cov=src/gside --cov-report=term-missing
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Layout: `tests/unit/` = pure, hermetic, milliseconds; `tests/integration/` =
|
|
92
|
+
needs real binaries or databases (gated by `skipif`, never mock a binary —
|
|
93
|
+
either run it or skip). `TestSpeciesMarker` needs `blastn` resolvable via
|
|
94
|
+
`GSIDE_PIXI_BIN` or pixi env (discovery order: `src/gside/config.py`).
|
|
95
|
+
CI (`.github/workflows/test.yml`, pip channel): ruff check + format --check,
|
|
96
|
+
`pyright src/gside/` (must stay zero), `pytest -q --cov=src/gside`.
|
|
97
|
+
Binary-needing tests skip gracefully without conda tools; full runs happen
|
|
98
|
+
in the pixi dev env locally.
|
|
99
|
+
Baseline (2026-10-03): 365 tests, total coverage **97%** — all modules 100%
|
|
100
|
+
except: ani 93%, multigene 98%, taxonomic 93% (quarantined), cli 99%,
|
|
101
|
+
db 92%, kma 94%, kmer 99%, read_mapper 99%. Enforced by `[tool.coverage.report] fail_under = 95` in `pyproject.toml`;
|
|
102
|
+
raise it every time coverage climbs.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Test-Driven Development (TDD)
|
|
107
|
+
|
|
108
|
+
TDD is mandatory for all behavior changes and bug fixes. No exceptions.
|
|
109
|
+
|
|
110
|
+
### The loop
|
|
111
|
+
|
|
112
|
+
1. **Red** — write a failing test FIRST that pins the desired behavior
|
|
113
|
+
(unit test if the logic is pure, integration test if it needs a binary/DB).
|
|
114
|
+
Run it, watch it fail for the right reason.
|
|
115
|
+
2. **Green** — write the minimal implementation that makes it pass.
|
|
116
|
+
3. **Refactor** — clean up with tests green; re-run the full suite + ruff.
|
|
117
|
+
|
|
118
|
+
### Rules
|
|
119
|
+
|
|
120
|
+
- **One behavior per test** — a test name states the behavior
|
|
121
|
+
(`test_ani_beats_marker`, not `test_arbitrate2`).
|
|
122
|
+
- **New/changed code ships with tests.** A PR without a covering test is
|
|
123
|
+
incomplete: pure helpers → `tests/unit/`; binary/DB paths →
|
|
124
|
+
`tests/integration/` with `skipif` guards, never mocked binaries.
|
|
125
|
+
- **Bug fix = regression test first.** Reproduce with a failing test, then fix.
|
|
126
|
+
- **Coverage ratchet.** Canonical command is `pixi run -e dev cov` (explicit
|
|
127
|
+
`--cov-fail-under`). The `fail_under` value in `pyproject.toml` IS enforced
|
|
128
|
+
by pytest-cov whenever `--cov` runs without an explicit flag value, so the
|
|
129
|
+
config and the flags must stay equal. CI checkouts lack the gitignored
|
|
130
|
+
databases, so the CI ceiling sits ~1pt below local (CI ~96% vs local 97%)
|
|
131
|
+
— the floor is 95 to hold in BOTH environments; local coverage must still
|
|
132
|
+
never go below its last high-water mark. Raise the floor in `pyproject.toml`,
|
|
133
|
+
the `cov` task, the CI workflow, and this file together — `test_floors_agree`
|
|
134
|
+
fails otherwise. Capture exit codes **without pipes** (`cmd > log; echo $?`),
|
|
135
|
+
pipes mask them.
|
|
136
|
+
- **Deterministic tests only.** No network, no wall-clock, no absolute paths
|
|
137
|
+
outside the repo (use `tmp_path` + `monkeypatch`). Real binaries are fine;
|
|
138
|
+
the network is not.
|
|
139
|
+
- **Fast unit suite.** `tests/unit` must finish in seconds; anything slower
|
|
140
|
+
belongs in `tests/integration`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Code Style Guidelines
|
|
145
|
+
|
|
146
|
+
### Formatting
|
|
147
|
+
- Line length: **100 characters** (see `pyproject.toml`; not the ruff default 88)
|
|
148
|
+
- Indentation: **4 spaces** (never tabs)
|
|
149
|
+
- String quotes: **double quotes** (`"..."`)
|
|
150
|
+
- Keep functions small; JSON payloads are built as plain dicts
|
|
151
|
+
|
|
152
|
+
### Imports
|
|
153
|
+
- Use **absolute imports** only (`from gside.config import ...`); no relative imports
|
|
154
|
+
- Import order: stdlib → third-party → local (ruff isort-compatible)
|
|
155
|
+
- No wildcard imports (`from module import *`)
|
|
156
|
+
|
|
157
|
+
### Naming Conventions
|
|
158
|
+
| Construct | Convention | Example |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| Module/file | `snake_case` | `multigene_identifier.py` |
|
|
161
|
+
| Function | `snake_case` | `identify_multigene()` |
|
|
162
|
+
| Variable | `snake_case` | `db_dir` |
|
|
163
|
+
| Class | `PascalCase` | `MultiGeneResult` |
|
|
164
|
+
| Constant | `UPPER_SNAKE_CASE` | `_MIN_IDENTITY` |
|
|
165
|
+
| Private | leading underscore | `_arbitrate()` |
|
|
166
|
+
|
|
167
|
+
### Type Annotations
|
|
168
|
+
- **Always annotate** function signatures (parameters + return type)
|
|
169
|
+
- Use built-in generics (`list[str]`, `dict[str, Any]`) over `typing.List` / `typing.Dict`
|
|
170
|
+
- Use `X | Y` union syntax over `Union[X, Y]`
|
|
171
|
+
|
|
172
|
+
### Error Handling
|
|
173
|
+
- `gside species` **always exits 0** — per-method failures go into
|
|
174
|
+
`methods.<mode>.error`, never raise through the CLI
|
|
175
|
+
- `gside db setup` returns 1 only when a result message contains `"ERROR"`
|
|
176
|
+
- Never silence exceptions with empty `except` blocks
|
|
177
|
+
- Prefer `pathlib.Path` over `os.path` for filesystem operations
|
|
178
|
+
|
|
179
|
+
### CLI Patterns
|
|
180
|
+
- Click group style (mirrors gmlst/gapit family): `HELP_SETTINGS` (`-h`/`--help`), bare invocation prints help and exits 2 (`no_args_is_help`), `-v`/`-q` mutually exclusive (UsageError), thin commands registered via `add_command`
|
|
181
|
+
- Every option carries a short flag: species `-m/--mode -f/--format -D/--db-dir -o/--output`; db `-t/--tier -s/--source -f/--format`
|
|
182
|
+
- Agent-friendly outputs: species defaults to JSON on stdout; `-o` writes the payload to a file (stdout stays empty, notice on stderr); `db status|list` accept `--format table|tsv|json`; `db setup` accepts `--format text|json` (json routes progress to stderr); `-V -J` prints version as JSON
|
|
183
|
+
- Subcommands: `species` / `db` group (`status|setup|list`) / `validate` / `--version`; entry `gside.cli:main`
|
|
184
|
+
- `--mode` choices are exactly `marker|panel|mash_refseq|sourmash|all` — do not invent modes
|
|
185
|
+
- `--db-dir` overrides the database root for one invocation
|
|
186
|
+
|
|
187
|
+
### File & Module Organization
|
|
188
|
+
```
|
|
189
|
+
src/gside/
|
|
190
|
+
├── __init__.py
|
|
191
|
+
├── cli.py # Entry point; all-mode runner + _arbitrate()
|
|
192
|
+
├── config.py # DATA_DIR / SPECIES_DB_DIR / pixi binary discovery
|
|
193
|
+
├── db.py # gside db status/setup/list + downloaders
|
|
194
|
+
├── engine/ # Algorithm abstraction (mostly unused, see gotchas)
|
|
195
|
+
│ ├── hits.py # Unified Hit dataclass
|
|
196
|
+
│ ├── registry.py # Backend registry
|
|
197
|
+
│ ├── read_mapper.py # ReadMapper facade
|
|
198
|
+
│ └── backends/ # blast / skani / kmer / minimap2 / kma / mmseqs2
|
|
199
|
+
└── analysis/
|
|
200
|
+
├── multigene_identifier.py # L1 marker rules (blastn, live)
|
|
201
|
+
├── ani_identifier.py # L2 panel/mash (live)
|
|
202
|
+
├── sourmash_identifier.py # L2 sourmash (needs external binary + GTDB db)
|
|
203
|
+
├── taxonomic_validator.py # L3 (gside validate; standard needs CheckM2/GTDB-Tk env)
|
|
204
|
+
└── species_canon.py # DORMANT (no importers)
|
|
205
|
+
tests/
|
|
206
|
+
└── unit/
|
|
207
|
+
└── test_cli.py # Real-BLAST smoke + arbitration unit tests
|
|
208
|
+
data/
|
|
209
|
+
├── db/
|
|
210
|
+
│ ├── D1_marker/ # mini tier, TRACKED in git: rules + fasta + blastdb
|
|
211
|
+
│ ├── D2_ani/ # panel tier (skani sketch + genomes), gitignored
|
|
212
|
+
│ │ # except manifests/ (tracked build recipe)
|
|
213
|
+
│ ├── D3_mash/ # mash tier (mash.msh), gitignored
|
|
214
|
+
│ └── D4_sourmash/ # GTDB reps k31 + lineages, via db setup (3.9GB)
|
|
215
|
+
docs/
|
|
216
|
+
├── index.md / index.zh.md # + architecture|installation|usage|reference/, same pairing
|
|
217
|
+
└── (nav in mkdocs.yml; en default, zh secondary; build with --strict)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- Keep modules focused and small; no circular imports
|
|
221
|
+
- L1 runtime data lives in `data/db/D1_marker/` (tracked); large L2 databases are
|
|
222
|
+
gitignored and provisioned via `gside db setup`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## pyproject.toml Conventions
|
|
227
|
+
|
|
228
|
+
```toml
|
|
229
|
+
[tool.ruff]
|
|
230
|
+
target-version = "py312"
|
|
231
|
+
line-length = 100
|
|
232
|
+
|
|
233
|
+
[tool.pytest.ini_options]
|
|
234
|
+
testpaths = ["tests"]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Key Rules for Agents
|
|
240
|
+
|
|
241
|
+
1. **Run `pixi run -e dev check` after every code change** (ruff check + format check; `fix` to auto-fix)
|
|
242
|
+
2. **Never use `# type: ignore` or `# noqa` without an explanatory comment**
|
|
243
|
+
3. **Never commit secrets** — tunables go into `marker_rules.yaml` / env vars, not code
|
|
244
|
+
4. **Prefer `pathlib.Path` over `os.path`** for all filesystem operations
|
|
245
|
+
5. **All functions must have type annotations** — no untyped signatures
|
|
246
|
+
6. **One responsibility per module** — don't let files grow into god-modules
|
|
247
|
+
7. **Don't add conda deps without updating `pixi.toml`** — use `pixi add <pkg>`; PyPI-only packages belong in `[feature.dev.pypi-dependencies]`, never in `[dependencies]` (conda channels don't carry them, e.g. `mkdocs-static-i18n`)
|
|
248
|
+
8. **Do not rename `species_markers_v2`** — it is the JSON output contract name (`database.name`), not a filename
|
|
249
|
+
9. **Do not wire up `species_canon.py`** — dormant with no importers; touching it is a separate project (`taxonomic_validator.py` is wired via `gside validate` since Cycle 19)
|
|
250
|
+
10. **Docs come in en/zh pairs** — every `docs/**/*.md` change needs the matching `.zh.md` update, and `mkdocs build --strict` must pass
|
|
251
|
+
11. **TDD is non-negotiable** — failing test first, then implementation; every new line covered; coverage never goes down (see TDD section)
|
|
252
|
+
12. **Keep `pyright` at zero and CI green** — `pixi run -e dev typecheck` must report 0 errors (quarantined files are excluded in `pyproject.toml`, not waived); `.github/workflows/test.yml` must pass before merge
|
|
253
|
+
|
|
254
|
+
## Known Gotchas (read before touching these areas)
|
|
255
|
+
|
|
256
|
+
- **Binary discovery**: `GSIDE_PIXI_BIN` env → repo `.pixi` → `~/.pixi` → hardcoded hermes-bacmap fallback path → `PATH` (`config.py`). The hardcoded path is machine-specific; prefer `GSIDE_PIXI_BIN` on other machines.
|
|
257
|
+
- **`gside species` never fails loud**: check `methods.<mode>.error` in JSON, not the exit code.
|
|
258
|
+
- **Marker thresholds are global**: `_MIN_IDENTITY=85.0` / `_MIN_COVERAGE=60.0` / `_HIGH_CONF=90.0`; rule-level `min_coverage` in YAML is currently **ignored** by the engine; `priority_over` is dead code.
|
|
259
|
+
- **Panel thresholds**: ANI ≥ 95 + AF ≥ 0.65 → high; 93–95 → medium. Mash identity ≥ 0.97 → high, 0.90–0.97 → medium.
|
|
260
|
+
- **Arbitration is layer-first**: `(layer, confidence)` tuple — any L2 hit beats any L1 hit (`cli.py:_arbitrate`).
|
|
261
|
+
- **`sourmash` needs its GTDB db**: provisioned via pixi (`sourmash>=4.9`) + `gside db setup --tier sourmash` (~3.9GB rs226 reps, renamed in place); `gather` output is read from `-o` file, never `--csv -` (unsupported flag).
|
|
262
|
+
- **DB download verification**: `db.py` pins digests (`MASH_MD5`, `PANEL_SHA256`, `SOURMASH_LINEAGES_SHA256`); empty constants (`SOURMASH_SIG_SHA256`) mean warn-and-skip. Tar extraction uses `filter="data"`. All pinned artifacts live in the GitHub release tag `db-v0.1` (panel) — re-pin `PANEL_SHA256` whenever the release asset is replaced.
|
|
263
|
+
- **`engine/` is wired for the species-ID hot paths**: marker → `BlastBackend`, panel → `SkaniBackend`, mash_refseq → `MashBackend` (analysis layer resolves binaries via `require_bin` and injects them with `binary=`; outputs are byte-equivalent to the former inline subprocess calls). Shared scaffolding lives in `engine/_env.py` (`require_bin`/`run_checked`; backends pass their module-level `which` as `resolver=` to keep test monkeypatch points). Still unconsumed: `KmaBackend`, `Mmseqs2Backend`, `MinimapBackend`, `ReadMapper`, `SequenceMatcher` — `KmaBackend` imports cleanly (`parse_db_header` ported verbatim into `gside/utils.py`) but has no in-repo callers and no provisioned binary.
|
|
264
|
+
- **FASTA header contract**: `>markers~~~<gene>~~~<desc> role=<primary|confirm|typing|virulence>`; parser takes `split("~~~")[1]` — the `markers` prefix itself is never validated.
|
gside-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Renton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gside-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gside
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Genome Species IDentification Engine — multi-layer species identification CLI (marker genes / ANI / sketch / consensus)
|
|
5
|
+
Author-email: indexofire <indexofire@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: <3.13,>=3.12
|
|
9
|
+
Requires-Dist: biopython>=1.83
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: pyyaml>=6.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: mkdocs-material>=9.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'dev'
|
|
15
|
+
Requires-Dist: mkdocs>=1.6; extra == 'dev'
|
|
16
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
17
|
+
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# gside(Genome Species IDentification Engine)
|
|
24
|
+
|
|
25
|
+
独立自包含的基因组物种鉴定 CLI:blastn 靶基因组合规则打底,skani / mash / sourmash
|
|
26
|
+
三路比对补充,`all` 模式按层仲裁,最终输出单一 JSON 判定。
|
|
27
|
+
|
|
28
|
+
## 鉴定模式
|
|
29
|
+
|
|
30
|
+
| mode | 底层引擎 | 原理 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `marker`(默认) | blastn | 靶基因组合规则(38 条规则,81 条参考序列) |
|
|
33
|
+
| `panel` | skani | ANI 比对精选参考面板(291 基因组) |
|
|
34
|
+
| `mash_refseq` | mash | RefSeq MinHash 距离 |
|
|
35
|
+
| `sourmash` | sourmash | GTDB gather |
|
|
36
|
+
| `all` | 上述全部 | 顺序执行 4 种方法并按层仲裁 |
|
|
37
|
+
|
|
38
|
+
## 快速开始
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pixi install && pip install -e .
|
|
42
|
+
|
|
43
|
+
# 物种鉴定(--mode 可选 marker/panel/mash_refseq/sourmash/all,默认 marker)
|
|
44
|
+
gside species contigs.fna --mode marker
|
|
45
|
+
gside species contigs.fna --mode panel
|
|
46
|
+
gside species contigs.fna --mode mash_refseq
|
|
47
|
+
gside species contigs.fna --mode sourmash
|
|
48
|
+
gside species contigs.fna --mode all
|
|
49
|
+
|
|
50
|
+
# 数据库管理(panel/mash/sourmash 需先下载对应库)
|
|
51
|
+
gside db status
|
|
52
|
+
gside db setup --tier panel
|
|
53
|
+
|
|
54
|
+
gside --version
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 判定阈值
|
|
58
|
+
|
|
59
|
+
- marker:命中需同时满足 identity >= 85% 且 coverage >= 60%(全局阈值,逐 hit 过滤);
|
|
60
|
+
规则字段 `min_hits`(默认 1)、`min_identity`(默认 90);命中基因平均
|
|
61
|
+
identity >= 90% 判 high,否则 medium。
|
|
62
|
+
- panel:ANI >= 95% 且 aligned_fraction >= 0.65 判 high;93% <= ANI < 95% 且
|
|
63
|
+
aligned_fraction >= 0.65 判 medium。
|
|
64
|
+
- mash_refseq:identity >= 0.97 判 high;0.90 <= identity < 0.97 判 medium,更低为 low。
|
|
65
|
+
|
|
66
|
+
## 仲裁(all 模式)
|
|
67
|
+
|
|
68
|
+
按 marker、panel、mash_refseq、sourmash 顺序执行,随后仲裁:
|
|
69
|
+
|
|
70
|
+
- panel / mash_refseq / sourmash 为第 2 层,marker 为第 1 层,层高者优先
|
|
71
|
+
- 层优先于置信度,同层内 high 优先
|
|
72
|
+
- 单法失败不终止其余方法,错误写入该方法在 JSON 中的条目
|
|
73
|
+
|
|
74
|
+
## 数据目录
|
|
75
|
+
|
|
76
|
+
| 变量 | 默认 | 内容 |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `GSIDE_DATA_DIR` | `<repo>/data` | 参考数据根目录(规则、fasta、预建库) |
|
|
79
|
+
| `GSIDE_DB_DIR` | `<data>/db` | 大型数据库(panel / mash sketch 等) |
|
|
80
|
+
| `GSIDE_PIXI_BIN` | 自动发现 | 生信二进制目录(blastn / skani / mash) |
|
|
81
|
+
|
|
82
|
+
数据分层:
|
|
83
|
+
|
|
84
|
+
- mini:随仓库分发,含 marker_rules.yaml(38 条规则)、markers.fasta(81 序列)
|
|
85
|
+
与预建 BLAST 库,开箱即用
|
|
86
|
+
- panel:skani 精选面板 sketch,约 130MB
|
|
87
|
+
- mash:RefSeq MinHash sketch,约 179MB
|
|
88
|
+
|
|
89
|
+
## 自定义规则
|
|
90
|
+
|
|
91
|
+
编辑 `data/db/D1_marker/marker_rules.yaml`,字段为 `species` / `genes` /
|
|
92
|
+
`min_hits` / `min_identity` / `exclude_genes`。修改 fasta 后重建 BLAST 库即生效,
|
|
93
|
+
无需改代码:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
makeblastdb -in markers.fasta -dbtype nucl -out markers_blastdb
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 输出
|
|
100
|
+
|
|
101
|
+
统一 JSON 输出(stdout)。顶层字段:`analysis_type` / `tool` / `version` /
|
|
102
|
+
`contigs` / `methods` / `verdict`。`methods` 内 panel、mash_refseq、sourmash 为
|
|
103
|
+
`result` 嵌套结构,marker 为扁平结构(无 `result` key)。`gside species` 恒以
|
|
104
|
+
退出码 0 结束,单法错误写入 JSON,不影响其余方法。
|
|
105
|
+
|
|
106
|
+
## 验证状态
|
|
107
|
+
|
|
108
|
+
- marker 法 17/17 金标准基因组(含 10 株近缘阴性对照)
|
|
109
|
+
- 3/3 新增物种真实参考株(C. jejuni / C. coli / S. pyogenes)
|
|
110
|
+
- panel 法 17/17(291 基因组精选面板)
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|