taxdumpy 1.2.1__tar.gz → 1.3.0__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.
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/.gitignore +6 -0
- taxdumpy-1.3.0/AGENTS.md +287 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/PKG-INFO +175 -43
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/README.md +173 -41
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/pytest.ini +1 -1
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/__init__.py +8 -1
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/basic.py +9 -4
- taxdumpy-1.3.0/src/taxdumpy/cache.py +43 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/cli.py +15 -55
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/database.py +66 -29
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/functions.py +197 -111
- taxdumpy-1.3.0/src/taxdumpy/ranks.py +64 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/taxdb.py +115 -85
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/taxon.py +20 -31
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/taxsqlite.py +126 -92
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/conftest.py +13 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_api_consistency.py +34 -2
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_basic.py +8 -1
- taxdumpy-1.3.0/tests/test_cache.py +82 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_cli.py +23 -5
- taxdumpy-1.3.0/tests/test_critical_regressions.py +158 -0
- taxdumpy-1.3.0/tests/test_descendants.py +137 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_error_handling.py +14 -2
- taxdumpy-1.3.0/tests/test_fast_cache.py +44 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_functions.py +142 -43
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_taxdb.py +22 -4
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_taxon.py +35 -5
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_taxsqlite.py +22 -1
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/.coveragerc +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/LICENSE +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/docs/incomplete_lineages.md +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/pyproject.toml +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/scripts/release.py +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/__main__.py +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/src/taxdumpy/ansi.py +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/__init__.py +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/delnodes.dmp +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/division.dmp +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/kept_taxids.list +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/make_smalldb.sh +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/merged.dmp +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/names.dmp +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/data/nodes.dmp +0 -0
- {taxdumpy-1.2.1 → taxdumpy-1.3.0}/tests/test_integration.py +0 -0
|
@@ -206,6 +206,12 @@ marimo/_static/
|
|
|
206
206
|
marimo/_lsp/
|
|
207
207
|
__marimo__/
|
|
208
208
|
|
|
209
|
+
# Taxdump caches generated beside the .dmp files when tests or tools run
|
|
210
|
+
taxdump.pickle
|
|
211
|
+
taxdump_fast.pickle
|
|
212
|
+
taxdump.sqlite
|
|
213
|
+
taxdump.sqlite.*.tmp
|
|
214
|
+
|
|
209
215
|
# More
|
|
210
216
|
.DS_Store
|
|
211
217
|
CLAUDE.md
|
taxdumpy-1.3.0/AGENTS.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Repository Guidance
|
|
2
|
+
|
|
3
|
+
Instructions and verified project notes for agents working in this repository.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
- Do not add "Co-Authored-By" messages in git commits
|
|
8
|
+
|
|
9
|
+
## Project Overview
|
|
10
|
+
|
|
11
|
+
**Taxdumpy** is a high-performance Python toolkit for parsing NCBI Taxonomy databases with lineage resolution and fuzzy search capabilities. Designed for metagenomics, phylogenetics, and bioinformatics applications.
|
|
12
|
+
|
|
13
|
+
- **Version source**: `src/taxdumpy/__init__.py` (Hatch reads the version from this file)
|
|
14
|
+
- **Python**: 3.10+
|
|
15
|
+
- **License**: MIT
|
|
16
|
+
- **Repository visibility**: GitHub repository `omegahh/taxdumpy` is private.
|
|
17
|
+
Codecov coverage uploads require a Codecov account and repository token; the
|
|
18
|
+
current CI upload is non-blocking if Codecov rejects or cannot process it.
|
|
19
|
+
|
|
20
|
+
## Development Commands
|
|
21
|
+
|
|
22
|
+
### Installation and Setup
|
|
23
|
+
```bash
|
|
24
|
+
# Install in development mode
|
|
25
|
+
python -m pip install -e .
|
|
26
|
+
|
|
27
|
+
# Install with dependencies
|
|
28
|
+
python -m pip install -e '.[dev]'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Running the CLI
|
|
32
|
+
```bash
|
|
33
|
+
# Cache taxonomy database (requires NCBI taxdump files)
|
|
34
|
+
taxdumpy cache -d /path/to/taxdump
|
|
35
|
+
|
|
36
|
+
# Search for organisms with fuzzy matching
|
|
37
|
+
taxdumpy search --fast "Escherichia coli"
|
|
38
|
+
|
|
39
|
+
# Get lineage for a specific taxid
|
|
40
|
+
taxdumpy lineage 511145
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`search --fast` and `lineage --fast` need a filtered pickle cache created with
|
|
44
|
+
`cache -f FILE`. Without `--fast`, both commands use SQLite to avoid loading the
|
|
45
|
+
full pickle for a single query. Python batch callers should create one `TaxDb`
|
|
46
|
+
instance and reuse it for all queries.
|
|
47
|
+
|
|
48
|
+
### Testing and Quality
|
|
49
|
+
```bash
|
|
50
|
+
# Run all tests with coverage (requires 80% minimum)
|
|
51
|
+
pytest
|
|
52
|
+
|
|
53
|
+
# Run specific test categories
|
|
54
|
+
pytest -m "not slow and not integration" # Fast tests only
|
|
55
|
+
pytest -m "slow" # Slow tests only
|
|
56
|
+
pytest -m "integration" # Integration tests only
|
|
57
|
+
pytest -m "unit or not (slow or integration)" # Unit and unmarked non-slow tests
|
|
58
|
+
|
|
59
|
+
# Run tests with coverage report
|
|
60
|
+
pytest --cov=taxdumpy --cov-report=html --cov-report=term-missing
|
|
61
|
+
|
|
62
|
+
# Run a focused test file without the repository-wide coverage threshold
|
|
63
|
+
pytest tests/test_taxdb.py --no-cov
|
|
64
|
+
|
|
65
|
+
# Format code and sort imports with ruff
|
|
66
|
+
ruff format src/ tests/ # Format code
|
|
67
|
+
ruff check src/ tests/ --fix # Fix import sorting
|
|
68
|
+
|
|
69
|
+
# Check formatting and imports in CI mode
|
|
70
|
+
ruff format --check src/ tests/
|
|
71
|
+
ruff check src/ tests/ # Only checks import sorting (I rules)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Note**: Ruff is configured with `line-length = 120`, and only handles formatting and import sorting (I rules). All other linting is disabled.
|
|
75
|
+
|
|
76
|
+
**Note**: pytest config lives in `pytest.ini` under a `[pytest]` section. It must
|
|
77
|
+
not be `[tool:pytest]` — that is the `setup.cfg` spelling, and pytest silently
|
|
78
|
+
ignores the whole file, taking `--strict-markers` and the 80% coverage gate with
|
|
79
|
+
it. Running `pytest` requires `pytest-cov` to be installed, since `addopts`
|
|
80
|
+
passes `--cov` unconditionally.
|
|
81
|
+
|
|
82
|
+
Test fixtures copy the `.dmp` files into temporary directories, and backend
|
|
83
|
+
caches are written there. Pytest also writes coverage reports in the repository
|
|
84
|
+
root; those reports and cache filenames are gitignored.
|
|
85
|
+
|
|
86
|
+
### Build and Release
|
|
87
|
+
```bash
|
|
88
|
+
# Build an sdist and wheel (requires the `build` package)
|
|
89
|
+
python -m build
|
|
90
|
+
|
|
91
|
+
# A pushed v* tag triggers GitHub Release creation; it does not publish to PyPI.
|
|
92
|
+
# Build and upload separately with Twine when a PyPI release is intended.
|
|
93
|
+
# python -m twine upload dist/*
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`scripts/release.py` has remote and repository side effects: it requires a clean
|
|
97
|
+
worktree, updates the version, commits it, pushes `origin main` and a version
|
|
98
|
+
tag, removes the existing `dist/` directory, and builds the package. Its
|
|
99
|
+
`--upload` option publishes to PyPI. Read the script and use it only when those
|
|
100
|
+
exact actions are intended.
|
|
101
|
+
|
|
102
|
+
## Architecture Overview
|
|
103
|
+
|
|
104
|
+
### Project Structure
|
|
105
|
+
```
|
|
106
|
+
src/taxdumpy/
|
|
107
|
+
├── __init__.py # Package initialization and exports
|
|
108
|
+
├── __main__.py # CLI entry point
|
|
109
|
+
├── ansi.py # ANSI color formatting utilities
|
|
110
|
+
├── basic.py # Core data structures (Node) and exception hierarchy
|
|
111
|
+
├── ranks.py # Shared canonical rank names, levels and codes
|
|
112
|
+
├── database.py # Abstract base class (TaxonomyDatabase) + factory function
|
|
113
|
+
├── cache.py # Source signatures and atomic pickle replacement
|
|
114
|
+
├── taxdb.py # Dictionary-based in-memory backend with pickle caching
|
|
115
|
+
├── taxsqlite.py # SQLite-based persistent backend
|
|
116
|
+
├── taxon.py # Taxon entity with lineage resolution
|
|
117
|
+
├── functions.py # Rank utilities (upper_rank_id, get_canonical_ranks) + subtree traversal (descendants)
|
|
118
|
+
└── cli.py # CLI commands: cache, search, lineage
|
|
119
|
+
|
|
120
|
+
tests/
|
|
121
|
+
├── conftest.py # Pytest fixtures
|
|
122
|
+
├── data/ # Sample taxdump files for testing
|
|
123
|
+
├── test_api_consistency.py
|
|
124
|
+
├── test_basic.py
|
|
125
|
+
├── test_cache.py
|
|
126
|
+
├── test_cli.py
|
|
127
|
+
├── test_critical_regressions.py # Pins previously-shipped critical defects
|
|
128
|
+
├── test_descendants.py # Subtree traversal, parametrized over both backends
|
|
129
|
+
├── test_error_handling.py
|
|
130
|
+
├── test_fast_cache.py
|
|
131
|
+
├── test_functions.py
|
|
132
|
+
├── test_integration.py
|
|
133
|
+
├── test_taxdb.py
|
|
134
|
+
├── test_taxon.py
|
|
135
|
+
└── test_taxsqlite.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Core Components
|
|
139
|
+
|
|
140
|
+
#### Database Layer
|
|
141
|
+
|
|
142
|
+
**TaxonomyDatabase** (`database.py`): Abstract base class defining the consistent API
|
|
143
|
+
- Abstract methods: `get_node()`, `_rapid_fuzz()`, `__len__()`, `__repr__()`
|
|
144
|
+
- Shared `search()` returns structured results; `fuzzy_search()` prints them
|
|
145
|
+
- Shared methods: `_validate_taxid()` - validates and converts taxid to integer
|
|
146
|
+
- Abstract cached properties: `all_names`, `name2taxid`, `delnodes`, `max_taxid_strlen`, `max_rank_strlen`
|
|
147
|
+
- Context manager support for connection management
|
|
148
|
+
- Formatting width properties return zero for an empty database
|
|
149
|
+
|
|
150
|
+
**Important**: these five properties use the module's `abstract_cached_property`
|
|
151
|
+
helper, not `@cached_property` over `@abstractmethod`. Stacking those two
|
|
152
|
+
silently breaks enforcement — `cached_property` does not propagate
|
|
153
|
+
`__isabstractmethod__`, so ABCMeta never sees the member and a subclass omitting
|
|
154
|
+
it instantiates fine, returning `None`. Do not "simplify" this back.
|
|
155
|
+
|
|
156
|
+
**TaxDb** (`taxdb.py`): Dictionary-based in-memory database
|
|
157
|
+
- Fast mode (`fast=True`) loads `taxdump_fast.pickle`, built from the taxids in a
|
|
158
|
+
filter file, their ancestors, and their descendants
|
|
159
|
+
- Deserializes a pickle once per initialization; reuse the instance for batches
|
|
160
|
+
- `build_fast_cache(taxids)` validates selections and writes the filtered cache
|
|
161
|
+
- New pickle caches include versioned source signatures; original three-part
|
|
162
|
+
pickles remain readable. Changes in available source sizes or mtimes trigger
|
|
163
|
+
a rebuild; caches remain usable when source files have been removed
|
|
164
|
+
- Pickle writes use atomic replacement. `rebuild=True` forces a full rebuild;
|
|
165
|
+
a failed rebuild preserves the previous cache
|
|
166
|
+
- Missing, stale or corrupt fast caches require an explicit filtered rebuild
|
|
167
|
+
- Data structures: `_taxid2nodes` (dict), `_old2news` (merged taxids), `_delnodes` (set)
|
|
168
|
+
|
|
169
|
+
**TaxSQLite** (`taxsqlite.py`): SQLite-based persistent database
|
|
170
|
+
- Tables: `nodes` (taxid, parent, rank, division), `names` (taxid, name, name_class), `merged` (oldtid, newtid), `delnodes` (taxid)
|
|
171
|
+
- All four tables are listed in `REQUIRED_TABLES`; `_check_tables` validates every one, so a cache built by an older version with a narrower schema is rebuilt rather than trusted
|
|
172
|
+
- The database is self-contained: no `.dmp` file is read at query time
|
|
173
|
+
- Builds go to a `mkstemp` temp path and are `os.replace`d into position, so an interrupted build cannot leave a schema-complete but empty database behind
|
|
174
|
+
- Indexed for fast queries
|
|
175
|
+
- Deleted-taxid checks use an indexed lookup rather than loading the whole set
|
|
176
|
+
- New caches record source signatures in `source_files`; legacy caches use
|
|
177
|
+
source mtimes. `rebuild=True` forces a rebuild with atomic replacement
|
|
178
|
+
- Memory-efficient for large datasets
|
|
179
|
+
|
|
180
|
+
**Factory Function**: `create_database(taxdump_dir, backend="sqlite", ...)`.
|
|
181
|
+
Supported backend names are `sqlite`/`sql` and `dict`/`memory`/`pickle`.
|
|
182
|
+
|
|
183
|
+
#### Data Structures
|
|
184
|
+
|
|
185
|
+
**Node** (`basic.py`): Dataclass with `__slots__` optimization
|
|
186
|
+
- Fields: `taxid`, `parent`, `rank`, `name`, `equal`, `acronym`, `division`
|
|
187
|
+
|
|
188
|
+
**Taxon** (`taxon.py`): Main entity for taxonomy operations
|
|
189
|
+
- Properties: `lineage`, `taxid_lineage`, `rank_lineage`, `name_lineage`
|
|
190
|
+
- Properties: `is_legacy`, `has_species_level`, `species_taxid`
|
|
191
|
+
- Traces lineage from any taxid to root, handles merged taxids automatically
|
|
192
|
+
|
|
193
|
+
#### Exception Hierarchy
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
TaxdumpyError (base)
|
|
197
|
+
├── TaxDbError # Database-related issues
|
|
198
|
+
│ ├── TaxdumpFileError # File issues (includes solutions)
|
|
199
|
+
│ └── DatabaseCorruptionError
|
|
200
|
+
├── TaxidError # Invalid/unknown taxid (includes suggestions)
|
|
201
|
+
├── TaxRankError # Invalid taxonomic rank (includes valid ranks)
|
|
202
|
+
├── TaxdumpyMemoryError
|
|
203
|
+
└── ValidationError # Input validation failures
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Rank Functions (`functions.py`)
|
|
207
|
+
|
|
208
|
+
- Rank tables live in `ranks.py` and are re-exported by `functions.py`
|
|
209
|
+
- `RankName` defines 9 levels plus the `domain` alias of `superkingdom`;
|
|
210
|
+
`RANKNAMES` is derived via `get_args` (species to realm)
|
|
211
|
+
- Realm uses the root-level `R` code to preserve existing integer encodings,
|
|
212
|
+
and `r__` in MPA output. Root itself is omitted from MPA output
|
|
213
|
+
- `EXTENDED_RANKS`: 32 names including sub-ranks and aliases
|
|
214
|
+
- `upper_rank_id()`: Get taxid at specific canonical rank (strict)
|
|
215
|
+
- `get_rank_taxid()`: Safely get taxid for rank (returns None if not found)
|
|
216
|
+
- `get_canonical_ranks()`: Get all canonical ranks in lineage as dict
|
|
217
|
+
- `get_closest_rank()`: Get closest rank at or above target level
|
|
218
|
+
- `get_rank_or_closest()`, `get_all_ranks()`, `find_rank_in_lineage()`
|
|
219
|
+
- `get_rank_distance()`: Calculate distance between two canonical ranks
|
|
220
|
+
- `group_taxids_by_rank()`: Group taxids by rank using their closest ancestor
|
|
221
|
+
|
|
222
|
+
#### Subtree Traversal (`functions.py`)
|
|
223
|
+
|
|
224
|
+
Lineage tracing walks *up*; these walk *down*.
|
|
225
|
+
|
|
226
|
+
- `descendants(taxdb, taxid, max_depth=None)`: every taxid below `taxid`,
|
|
227
|
+
excluding `taxid` itself and excluding deleted/merged taxids. Breadth-first.
|
|
228
|
+
`max_depth=1` gives direct children only
|
|
229
|
+
- `descendant_nodes()`: the same as `Node` objects (one `get_node` per result)
|
|
230
|
+
- `build_children_index(taxdb)`: inverts child→parent in one pass, cached per
|
|
231
|
+
database in a `WeakKeyDictionary` so a closed database does not pin a
|
|
232
|
+
million-entry index. Traversal is iterative, so deep trees cannot exhaust the
|
|
233
|
+
stack
|
|
234
|
+
|
|
235
|
+
These reach into `taxdb._taxid2nodes` and `taxsqlite._conn` for speed, so they
|
|
236
|
+
dispatch on backend type and raise `TaxDbError` for any third backend. Moving
|
|
237
|
+
them onto `TaxonomyDatabase` would remove that coupling.
|
|
238
|
+
|
|
239
|
+
### Data Flow
|
|
240
|
+
1. NCBI taxdump files (nodes.dmp, names.dmp, etc.) are parsed
|
|
241
|
+
2. Data is cached as either pickle files (TaxDb) or SQLite database (TaxSQLite)
|
|
242
|
+
3. Taxon objects provide lineage tracing and taxonomy operations
|
|
243
|
+
4. CLI commands leverage either backend for search/lineage operations
|
|
244
|
+
|
|
245
|
+
### CLI Commands
|
|
246
|
+
|
|
247
|
+
| Command | Description | Key Options |
|
|
248
|
+
|---------|-------------|-------------|
|
|
249
|
+
| `cache` | Build SQLite and full pickle caches; with `-f`, build only a filtered fast pickle | `-d/--directory`, `-f/--filter-file` |
|
|
250
|
+
| `search` | Fuzzy organism name search | `-d/--directory`, `--fast`, `--limit` |
|
|
251
|
+
| `lineage` | Display taxonomic lineage (SQLite by default, filtered pickle with `--fast`) | `-d/--directory`, `--fast` |
|
|
252
|
+
|
|
253
|
+
### Environment Variables
|
|
254
|
+
- `TAXDB_PATH`: Default path to taxonomy database (defaults to `~/.taxonkit`)
|
|
255
|
+
|
|
256
|
+
### Performance Features
|
|
257
|
+
- Pickle caching for repeated Python queries; `fast=True` loads a filtered cache
|
|
258
|
+
- SQLite backend for memory efficiency with large datasets
|
|
259
|
+
- Rapidfuzz integration for fuzzy organism name matching
|
|
260
|
+
- Fast cache mode for selected taxids and their related lineage/subtree
|
|
261
|
+
|
|
262
|
+
### Database Requirements
|
|
263
|
+
Building a new cache from source requires NCBI taxdump files:
|
|
264
|
+
- `nodes.dmp`: Taxonomy tree structure
|
|
265
|
+
- `names.dmp`: Scientific names
|
|
266
|
+
- `merged.dmp`: Legacy taxid mappings
|
|
267
|
+
- `delnodes.dmp`: Deleted nodes
|
|
268
|
+
- `division.dmp`: Division information
|
|
269
|
+
|
|
270
|
+
### Dependencies
|
|
271
|
+
- **Core**: `rapidfuzz` (fuzzy matching), `tqdm` (progress bars)
|
|
272
|
+
- **Dev**: `ruff`, `pytest`, `pytest-cov`, `pytest-mock`, `build`
|
|
273
|
+
|
|
274
|
+
## Known Issues
|
|
275
|
+
|
|
276
|
+
These issues are still present in the checked-in implementation. Recheck this
|
|
277
|
+
section when the corresponding code or tests change.
|
|
278
|
+
|
|
279
|
+
**Mutable nodes**: `TaxDb.get_node` returns stored Node objects while SQLite
|
|
280
|
+
returns fresh ones. Mutating dict nodes can invalidate cached search and
|
|
281
|
+
children indexes; treat returned nodes as read-only until an explicit mutation
|
|
282
|
+
API or immutable node contract is introduced.
|
|
283
|
+
|
|
284
|
+
**Importer differences**: malformed input handling differs between backends.
|
|
285
|
+
SQLite construction still holds parsed names in memory, so its build uses more
|
|
286
|
+
memory than steady-state queries. Node and insert rows are streamed, with
|
|
287
|
+
indexes built after bulk insertion.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: taxdumpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Python package for efficiently parsing NCBI's taxdump database
|
|
5
5
|
Project-URL: Documentation, https://github.com/omegahh/taxdumpy
|
|
6
6
|
Project-URL: Issues, https://github.com/omegahh/taxdumpy/issues
|
|
@@ -61,9 +61,10 @@ _A high-performance Python toolkit for parsing NCBI Taxonomy databases with line
|
|
|
61
61
|
|
|
62
62
|
## Features
|
|
63
63
|
|
|
64
|
-
- **
|
|
64
|
+
- **Cached Loading**: Parse NCBI taxdump files once, then reuse pickle or SQLite caches
|
|
65
65
|
- **Dual Backends**: In-memory (`TaxDb`) for speed or SQLite (`TaxSQLite`) for memory efficiency
|
|
66
66
|
- **Lineage Resolution**: Complete lineage tracing with automatic handling of merged/deleted nodes
|
|
67
|
+
- **Subtree Traversal**: Walk *down* the tree with `descendants` — every taxid below a given one
|
|
67
68
|
- **Rank Codes**: Comparable `RC` class for efficient rank-based operations and grouping
|
|
68
69
|
- **Fuzzy Search**: Rapid approximate name matching using `rapidfuzz`
|
|
69
70
|
- **Batch Grouping**: Group taxids by rank level with `group_taxids_by_rank`
|
|
@@ -86,7 +87,7 @@ pip install -e .
|
|
|
86
87
|
**With Development Dependencies:**
|
|
87
88
|
|
|
88
89
|
```bash
|
|
89
|
-
pip install -e .[dev]
|
|
90
|
+
pip install -e '.[dev]'
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
## Quick Start
|
|
@@ -112,7 +113,7 @@ print(human.rank_lineage)
|
|
|
112
113
|
|
|
113
114
|
# Check taxonomic properties
|
|
114
115
|
print(f"Rank: {human.rank}") # species
|
|
115
|
-
print(f"Division: {human.division}") #
|
|
116
|
+
print(f"Division: {human.division}") # PRI (division code, not full name)
|
|
116
117
|
print(f"Is legacy: {human.is_legacy}") # False
|
|
117
118
|
```
|
|
118
119
|
|
|
@@ -121,23 +122,54 @@ print(f"Is legacy: {human.is_legacy}") # False
|
|
|
121
122
|
```python
|
|
122
123
|
from taxdumpy import create_database, Taxon
|
|
123
124
|
|
|
124
|
-
#
|
|
125
|
-
db = create_database(
|
|
125
|
+
# taxdump_dir is the first positional argument
|
|
126
|
+
db = create_database("/path/to/taxdump", backend="sqlite")
|
|
126
127
|
# Or: backend="dict" for in-memory
|
|
127
128
|
|
|
128
129
|
taxon = Taxon(9606, db)
|
|
129
130
|
print(taxon.name) # Homo sapiens
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
### Subtree Traversal
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from taxdumpy import TaxDb, descendants
|
|
137
|
+
|
|
138
|
+
taxdb = TaxDb("/path/to/taxdump")
|
|
139
|
+
|
|
140
|
+
# Every taxid below genus Escherichia (transitively, not just children)
|
|
141
|
+
descendants(taxdb, 561)
|
|
142
|
+
# [562, 208962, 511145, ...]
|
|
143
|
+
|
|
144
|
+
# Direct children only
|
|
145
|
+
descendants(taxdb, 561, max_depth=1)
|
|
146
|
+
|
|
147
|
+
# Deleted and merged taxids are excluded; the queried taxid itself is not
|
|
148
|
+
# included. Combine with existing helpers to filter:
|
|
149
|
+
species = [t for t in descendants(taxdb, 2) if taxdb.get_node(t).rank == "species"]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The first call builds a parent-to-children index in one pass over the database
|
|
153
|
+
and caches it, so later queries cost time proportional to the subtree returned
|
|
154
|
+
rather than to the database size.
|
|
155
|
+
|
|
132
156
|
### Fuzzy Search
|
|
133
157
|
|
|
134
158
|
```python
|
|
135
|
-
#
|
|
136
|
-
|
|
159
|
+
# Print formatted, highlighted matches to the console
|
|
160
|
+
taxdb.fuzzy_search("Escherichia coli", limit=5)
|
|
161
|
+
|
|
162
|
+
# Or get the matches as data
|
|
163
|
+
results = taxdb.search("Escherichia coli", limit=5)
|
|
137
164
|
for match in results:
|
|
138
165
|
print(f"{match['name']} (TaxID: {match['taxid']}, Score: {match['score']})")
|
|
139
166
|
```
|
|
140
167
|
|
|
168
|
+
`search` returns dictionaries containing `name`, `taxid`, and a `score` from
|
|
169
|
+
0 to 100. It preserves separate taxa sharing a name and reuses its candidate
|
|
170
|
+
index across queries. `fuzzy_search` prints the results. Both methods propagate
|
|
171
|
+
validation and database errors to callers.
|
|
172
|
+
|
|
141
173
|
### Rank Codes and Grouping
|
|
142
174
|
|
|
143
175
|
```python
|
|
@@ -169,6 +201,10 @@ print(human.mpa_repr)
|
|
|
169
201
|
|
|
170
202
|
## Command Line Interface
|
|
171
203
|
|
|
204
|
+
By default, `search` and `lineage` use SQLite so individual queries do not load
|
|
205
|
+
the full pickle into memory. `--fast` loads a smaller, filtered pickle instead;
|
|
206
|
+
create that cache first with `cache -f FILE`.
|
|
207
|
+
|
|
172
208
|
### Check Version
|
|
173
209
|
|
|
174
210
|
```bash
|
|
@@ -178,13 +214,17 @@ taxdumpy --version
|
|
|
178
214
|
### Cache Database
|
|
179
215
|
|
|
180
216
|
```bash
|
|
181
|
-
#
|
|
217
|
+
# Rebuild both taxdump.sqlite and taxdump.pickle from source files
|
|
182
218
|
taxdumpy cache -d /path/to/taxdump
|
|
183
219
|
|
|
184
|
-
#
|
|
220
|
+
# Build taxdump_fast.pickle from selected taxids (one per line), their
|
|
221
|
+
# ancestors, and their descendants; blank lines and # comments are allowed
|
|
185
222
|
taxdumpy cache -d /path/to/taxdump -f important_taxids.txt
|
|
186
223
|
```
|
|
187
224
|
|
|
225
|
+
The filtered command loads the full `TaxDb` to build the subset. It does not
|
|
226
|
+
rebuild SQLite. Queries outside the subset require the full database.
|
|
227
|
+
|
|
188
228
|
### Search Operations
|
|
189
229
|
|
|
190
230
|
```bash
|
|
@@ -250,7 +290,7 @@ export TAXDB_PATH=~/.taxonkit
|
|
|
250
290
|
from taxdumpy import TaxSQLite, Taxon
|
|
251
291
|
|
|
252
292
|
# Use SQLite for memory-efficient storage
|
|
253
|
-
db = TaxSQLite("/path/to/taxdump") # Creates
|
|
293
|
+
db = TaxSQLite("/path/to/taxdump") # Creates taxdump.sqlite in that directory
|
|
254
294
|
|
|
255
295
|
# Same API as TaxDb
|
|
256
296
|
taxon = Taxon(9606, db)
|
|
@@ -264,11 +304,14 @@ with TaxSQLite("/path/to/taxdump") as db:
|
|
|
264
304
|
|
|
265
305
|
### Batch Processing
|
|
266
306
|
|
|
307
|
+
Load the full pickle once and reuse the same instance for all Python queries.
|
|
308
|
+
Its startup cost is paid once per instance, rather than once per taxid.
|
|
309
|
+
|
|
267
310
|
```python
|
|
268
311
|
from taxdumpy import TaxDb, Taxon
|
|
269
312
|
|
|
270
|
-
#
|
|
271
|
-
taxdb = TaxDb("/path/to/taxdump"
|
|
313
|
+
# Load all taxa for arbitrary batch queries
|
|
314
|
+
taxdb = TaxDb("/path/to/taxdump")
|
|
272
315
|
|
|
273
316
|
taxids = [9606, 511145, 7227, 4932] # Human, E.coli, Fly, Yeast
|
|
274
317
|
for taxid in taxids:
|
|
@@ -276,6 +319,43 @@ for taxid in taxids:
|
|
|
276
319
|
print(f"{taxon.name}: {' > '.join(taxon.name_lineage[:3])}")
|
|
277
320
|
```
|
|
278
321
|
|
|
322
|
+
For a known subset, build a filtered cache using the full database, then load
|
|
323
|
+
that smaller cache in subsequent processes:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
count = taxdb.build_fast_cache([9606, 511145, 7227, 4932])
|
|
327
|
+
print(f"Cached {count} nodes, including ancestors and descendants")
|
|
328
|
+
|
|
329
|
+
subset = TaxDb("/path/to/taxdump", fast=True)
|
|
330
|
+
human = Taxon(9606, subset)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Cache Freshness and Rebuilding
|
|
334
|
+
|
|
335
|
+
New caches record source file sizes and modification times. Opening a stale
|
|
336
|
+
full pickle or SQLite cache rebuilds it from the taxdump files. Missing, stale,
|
|
337
|
+
or unreadable filtered caches raise an error; recreate them with `cache -f FILE`
|
|
338
|
+
or `build_fast_cache(taxids)` so the selection is explicit.
|
|
339
|
+
|
|
340
|
+
Original three-part pickles remain readable, and legacy caches use source
|
|
341
|
+
modification times to detect changes. Valid caches remain usable after the
|
|
342
|
+
source `.dmp` files are removed. Rebuilding requires the source files again.
|
|
343
|
+
|
|
344
|
+
To force a rebuild from Python:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
from taxdumpy import TaxDb, TaxSQLite
|
|
348
|
+
|
|
349
|
+
taxdb = TaxDb("/path/to/taxdump", rebuild=True)
|
|
350
|
+
with TaxSQLite("/path/to/taxdump", rebuild=True) as db:
|
|
351
|
+
print(len(db))
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Each cache is replaced atomically only after its new contents are written
|
|
355
|
+
successfully. A failed rebuild preserves the previous file. `rebuild=True`
|
|
356
|
+
cannot be combined with `fast=True`; rebuild filtered caches through the full
|
|
357
|
+
database's `build_fast_cache` method instead.
|
|
358
|
+
|
|
279
359
|
### Handling Merged/Legacy TaxIDs
|
|
280
360
|
|
|
281
361
|
```python
|
|
@@ -293,7 +373,7 @@ print(taxon.node.taxid) # Current/resolved taxid
|
|
|
293
373
|
### Exception Handling
|
|
294
374
|
|
|
295
375
|
```python
|
|
296
|
-
from taxdumpy import TaxDb, Taxon, TaxidError, TaxRankError,
|
|
376
|
+
from taxdumpy import TaxDb, Taxon, TaxidError, TaxRankError, upper_rank_id
|
|
297
377
|
|
|
298
378
|
taxdb = TaxDb("/path/to/taxdump")
|
|
299
379
|
|
|
@@ -304,7 +384,8 @@ except TaxidError as e:
|
|
|
304
384
|
# Includes suggestions for similar taxids
|
|
305
385
|
|
|
306
386
|
try:
|
|
307
|
-
|
|
387
|
+
# Signature is (tax, taxdb, rank) — a Taxon, not a bare taxid
|
|
388
|
+
rank_id = upper_rank_id(Taxon(9606, taxdb), taxdb, "invalid_rank")
|
|
308
389
|
except TaxRankError as e:
|
|
309
390
|
print(f"Invalid rank: {e}")
|
|
310
391
|
# Includes list of valid ranks
|
|
@@ -317,32 +398,37 @@ except TaxRankError as e:
|
|
|
317
398
|
**TaxDb**: In-memory dictionary-based database with pickle caching
|
|
318
399
|
|
|
319
400
|
```python
|
|
320
|
-
TaxDb(taxdump_dir: str, fast: bool = False)
|
|
401
|
+
TaxDb(taxdump_dir: Path | str, fast: bool = False, verbose: bool = False, *, rebuild: bool = False)
|
|
321
402
|
```
|
|
322
403
|
|
|
323
404
|
**TaxSQLite**: SQLite-based persistent database
|
|
324
405
|
|
|
325
406
|
```python
|
|
326
|
-
TaxSQLite(taxdump_dir: str)
|
|
407
|
+
TaxSQLite(taxdump_dir: Path | str, verbose: bool = False, *, rebuild: bool = False)
|
|
327
408
|
```
|
|
328
409
|
|
|
329
410
|
**Taxon**: Taxonomic unit with lineage resolution
|
|
330
411
|
|
|
331
412
|
```python
|
|
332
|
-
Taxon(taxid: int, taxdb: TaxDb | TaxSQLite)
|
|
413
|
+
Taxon(taxid: int | str, taxdb: TaxDb | TaxSQLite)
|
|
333
414
|
|
|
334
415
|
# Properties
|
|
335
|
-
.name: str
|
|
336
|
-
.rank: str
|
|
337
|
-
.division: str
|
|
338
|
-
.parent: int
|
|
339
|
-
.
|
|
340
|
-
.
|
|
341
|
-
.
|
|
416
|
+
.name: str # Scientific name
|
|
417
|
+
.rank: str # Taxonomic rank
|
|
418
|
+
.division: str # NCBI division
|
|
419
|
+
.parent: int # Parent taxid
|
|
420
|
+
.acronym: str | None # Acronym, if any
|
|
421
|
+
.node: Node # Underlying node (resolved, if merged)
|
|
422
|
+
.rc: RC # Rank code for this taxon
|
|
423
|
+
.lineage: list[Node] # Complete lineage as Node objects
|
|
424
|
+
.name_lineage: list[str] # Names from taxid to root
|
|
425
|
+
.rank_lineage: list[str] # Ranks from taxid to root
|
|
342
426
|
.taxid_lineage: list[int] # Taxids from taxid to root
|
|
343
|
-
.
|
|
344
|
-
.
|
|
345
|
-
.
|
|
427
|
+
.rankcode_lineage: list[RC] # Rank codes from taxid to root
|
|
428
|
+
.mpa_repr: str # MetaPhlAn-style lineage string
|
|
429
|
+
.is_legacy: bool # True if taxid was merged
|
|
430
|
+
.has_species_level: bool # True if lineage contains species rank
|
|
431
|
+
.species_taxid: int | None # Taxid at species level, or None if absent (previously 0)
|
|
346
432
|
```
|
|
347
433
|
|
|
348
434
|
**Factory Function**
|
|
@@ -351,21 +437,52 @@ Taxon(taxid: int, taxdb: TaxDb | TaxSQLite)
|
|
|
351
437
|
from taxdumpy import create_database
|
|
352
438
|
|
|
353
439
|
# backend: "sqlite", "sql", "dict", "memory", or "pickle"
|
|
354
|
-
db = create_database(
|
|
440
|
+
db = create_database("/path/to/taxdump", backend="sqlite")
|
|
355
441
|
```
|
|
356
442
|
|
|
357
443
|
### Rank Functions
|
|
358
444
|
|
|
359
445
|
```python
|
|
360
446
|
from taxdumpy import (
|
|
361
|
-
upper_rank_id,
|
|
362
|
-
get_rank_taxid,
|
|
447
|
+
upper_rank_id, # Get taxid at rank (raises TaxRankError if not found)
|
|
448
|
+
get_rank_taxid, # Get taxid at rank (returns None if not found)
|
|
363
449
|
get_canonical_ranks, # Get dict of all canonical ranks in lineage
|
|
364
|
-
get_closest_rank,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
450
|
+
get_closest_rank, # Get closest rank at or above target level
|
|
451
|
+
get_rank_or_closest, # Same, returning the taxid alone
|
|
452
|
+
get_all_ranks, # Every rank in the lineage as a dict
|
|
453
|
+
find_rank_in_lineage,# First of several candidate ranks present
|
|
454
|
+
get_rank_distance, # Calculate distance between two ranks
|
|
455
|
+
group_taxids_by_rank,# Group a list of taxids at a given rank
|
|
368
456
|
)
|
|
457
|
+
|
|
458
|
+
# The rank tables live in taxdumpy.functions, not the package root:
|
|
459
|
+
from taxdumpy.functions import RANKNAMES, EXTENDED_RANKS
|
|
460
|
+
# RANKNAMES: 9 canonical levels, species → realm (domain/superkingdom are aliases)
|
|
461
|
+
# EXTENDED_RANKS: 32 names including sub-ranks and aliases
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
`domain` and `superkingdom` resolve to the same rank level. Realm uses `r__` in
|
|
465
|
+
MPA output and the root-level `R` rank code, preserving existing integer `RC`
|
|
466
|
+
values. Grouping by `root` always selects taxid 1, including viral lineages.
|
|
467
|
+
|
|
468
|
+
`upper_rank_id` requires the requested higher rank or its subrank to exist in
|
|
469
|
+
the lineage; otherwise it raises `TaxRankError`. Use `get_closest_rank` or
|
|
470
|
+
`get_rank_or_closest` when fallback to a higher canonical rank is desired.
|
|
471
|
+
|
|
472
|
+
### Subtree Traversal
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
from taxdumpy import descendants, descendant_nodes, build_children_index
|
|
476
|
+
|
|
477
|
+
descendants(taxdb, taxid, max_depth=None) -> list[int]
|
|
478
|
+
# Every taxid below `taxid`. Excludes `taxid` itself, and excludes deleted
|
|
479
|
+
# and merged taxids. max_depth=1 gives direct children only. Breadth-first.
|
|
480
|
+
|
|
481
|
+
descendant_nodes(taxdb, taxid, max_depth=None) -> list[Node]
|
|
482
|
+
# Same, as Node objects. Costs one get_node per result.
|
|
483
|
+
|
|
484
|
+
build_children_index(taxdb) -> dict[int, list[int]]
|
|
485
|
+
# The parent-to-children index the above build and cache. Read-only.
|
|
369
486
|
```
|
|
370
487
|
|
|
371
488
|
### Exceptions
|
|
@@ -384,11 +501,17 @@ from taxdumpy import (
|
|
|
384
501
|
|
|
385
502
|
## Performance Tips
|
|
386
503
|
|
|
387
|
-
- **
|
|
388
|
-
- **
|
|
504
|
+
- **Single CLI Queries**: Use the default SQLite backend to avoid loading the full pickle
|
|
505
|
+
- **Python Batches**: Load one `TaxDb` instance and reuse it for multiple operations
|
|
506
|
+
- **Filtered Data**: Use `TaxDb(path, fast=True)` after building a subset cache;
|
|
507
|
+
startup cost and memory use depend on the subset size
|
|
389
508
|
- **Environment Variables**: Set `TAXDB_PATH` to avoid repeating database paths
|
|
390
509
|
- **Choose Backend**: Use `TaxSQLite` for large datasets with limited memory
|
|
391
510
|
- **Batch Operations**: Process multiple TaxIDs in batches rather than individual calls
|
|
511
|
+
- **Subtree Queries**: Reuse one database instance so `descendants` shares its
|
|
512
|
+
cached children index. Building it costs one pass over the database
|
|
513
|
+
(~0.4 s for 1M nodes); every query afterwards is proportional to the subtree,
|
|
514
|
+
and small subtrees resolve in microseconds
|
|
392
515
|
|
|
393
516
|
## Use Cases
|
|
394
517
|
|
|
@@ -412,7 +535,7 @@ from taxdumpy import (
|
|
|
412
535
|
```bash
|
|
413
536
|
git clone https://github.com/omegahh/taxdumpy.git
|
|
414
537
|
cd taxdumpy
|
|
415
|
-
pip install -e .[dev]
|
|
538
|
+
pip install -e '.[dev]'
|
|
416
539
|
```
|
|
417
540
|
|
|
418
541
|
### Testing
|
|
@@ -439,12 +562,21 @@ ruff check src/ tests/ --fix
|
|
|
439
562
|
|
|
440
563
|
### Release Process
|
|
441
564
|
|
|
565
|
+
A pushed `v*` tag creates a GitHub Release. It does not build or publish a
|
|
566
|
+
package to PyPI. Build and upload separately when publishing a release:
|
|
567
|
+
|
|
442
568
|
```bash
|
|
443
|
-
|
|
444
|
-
python
|
|
569
|
+
python -m build
|
|
570
|
+
python -m twine upload dist/*
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
The release helper requires a clean worktree, runs tests and Ruff, updates and
|
|
574
|
+
commits the version, pushes `origin main` and a version tag, removes `dist/`,
|
|
575
|
+
and builds the package. `--upload` also publishes to PyPI. Run it only when
|
|
576
|
+
those repository and remote changes are intended:
|
|
445
577
|
|
|
446
|
-
|
|
447
|
-
|
|
578
|
+
```bash
|
|
579
|
+
python scripts/release.py --version 1.2.3 --upload
|
|
448
580
|
```
|
|
449
581
|
|
|
450
582
|
## License
|