Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
fcc27c2
This initializes a uv package in this repository.
gaurav Dec 2, 2025
876353d
Added basic CLI.
gaurav Dec 2, 2025
ec1d1f0
Add /data to the .gitignore.
gaurav Dec 2, 2025
eff8f26
Initial implementation of a basic xref query-er.
gaurav Dec 3, 2025
4d04e2a
Added a method to look up a particular identifier.
gaurav Dec 4, 2025
8531cb7
Added CURIE expansion/recursive lookup.
gaurav Dec 4, 2025
a1aeec6
Added a basic ConcordTester.
gaurav Dec 4, 2025
bb1eb99
Added labels via NodeNorm.
gaurav Dec 4, 2025
40c3338
Midnight commit: attempting to improve expansion.
gaurav Jan 8, 2026
8c41112
Added some improvements.
gaurav Jan 8, 2026
239c89f
Added a CLAUDE.md by Claude.ai.
gaurav Feb 14, 2026
8132fe1
Reorganized file slightly.
gaurav Feb 14, 2026
bd00972
Claude wrote some tests.
gaurav Feb 14, 2026
9cc06bc
Improved downloader using Claude.
gaurav Feb 14, 2026
da8bb0c
Added MD5 download functionality.
gaurav Feb 14, 2026
8f36b74
Removed empty model file.
gaurav Feb 15, 2026
0534fd8
Attempted to rename this package to babel-explorer.
gaurav Feb 15, 2026
0b3a9f5
Add comprehensive pytest suite for all core modules
gaurav Feb 15, 2026
8535202
Merge branch 'main' into basic-implementation-in-uv
gaurav Feb 17, 2026
ff0dacc
Added uv.lock (not sure why it wasn't added previously).
gaurav Mar 2, 2026
bacc72d
Update CLAUDE.md
gaurav Mar 2, 2026
96d9609
Update pyproject.toml
gaurav Mar 2, 2026
0c33e7e
Update src/babel_explorer/core/babel_xrefs.py
gaurav Mar 2, 2026
1aff013
Update src/babel_explorer/core/nodenorm.py
gaurav Mar 2, 2026
af76c15
Replace MD5 checksumming with HTTP header caching and freshness window
gaurav Mar 2, 2026
fb41da0
Added some CURIEs to test.
gaurav Mar 3, 2026
5c544a2
Partially changed --expand to --recurse.
gaurav Mar 3, 2026
280212a
More fully changed --expand to --recurse.
gaurav Mar 3, 2026
b522e6e
Add pytest-xdist for parallel test execution
gaurav Mar 3, 2026
e137c31
Replace Python recursion in get_curie_xrefs with DuckDB WITH RECURSIVE
gaurav Mar 3, 2026
b115d02
Fix xdist race condition: skip test-data cleanup in parallel runs
gaurav Mar 3, 2026
5a0f758
Made output a bit prettier.
gaurav Mar 3, 2026
be2fa36
Update src/babel_explorer/core/nodenorm.py
gaurav Mar 16, 2026
2b2aa7f
Simplify babel_xrefs: extract helper, remove dead fetches, fix defaul…
gaurav Mar 16, 2026
3cdd19c
Fix LabeledCrossReference: make it a frozen dataclass subclass
gaurav Mar 16, 2026
c7a3f16
Fix BabelDownloader: use tempfile.gettempdir() when local_path is None
gaurav Mar 16, 2026
c6635bc
Fix test-concord: guard against None from get_clique_identifiers
gaurav Mar 16, 2026
d74110e
Potential fix for pull request finding
gaurav Mar 16, 2026
4338bdc
Potential fix for pull request finding
gaurav Mar 16, 2026
be3e427
Potential fix for pull request finding
gaurav Mar 16, 2026
f8b718b
Potential fix for pull request finding
gaurav Mar 16, 2026
48c8e96
Potential fix for pull request finding
gaurav Mar 16, 2026
8fb37d6
Potential fix for pull request finding
gaurav Mar 16, 2026
49f5c3b
Potential fix for pull request finding
gaurav Mar 16, 2026
c952c12
Fix DuckDB connection leaks by using context managers
gaurav Mar 16, 2026
b0539bb
Fix and simplify test mocks for context manager protocol
gaurav Mar 16, 2026
6319212
Add configurable HTTP timeout to NodeNorm and BabelDownloader
gaurav Mar 16, 2026
b634d11
Fix _etag_matches docstring to match actual behavior
gaurav Mar 16, 2026
a7eb8c1
Got rid of ignore_curies_in_expansion, which is no longer used.
gaurav Mar 16, 2026
7163a64
Add ruff CI and fix all lint errors
gaurav Mar 16, 2026
f9e549e
Rename lint workflow to CI and add unit test job
gaurav Mar 16, 2026
7d1d5ca
Improved documentation.
gaurav Mar 16, 2026
0d9e32c
Add tests for parse_duration() in cli.py
gaurav Mar 16, 2026
0ca35eb
Add CliRunner tests for xrefs, ids, and test-concord commands
gaurav Mar 16, 2026
17782a2
Reformatted code with ruff.
gaurav Mar 26, 2026
d34c5c3
Update src/babel_explorer/cli.py
gaurav Mar 30, 2026
ec0a71c
Cache get_identifier() locals in _to_labeled_xref; root-anchor lib/ i…
gaurav Mar 30, 2026
67be81e
Fix bugs and gaps identified in PR #1 code review
gaurav Mar 30, 2026
4199ae2
Run integration tests on push to master and weekly on Tuesdays
gaurav Mar 30, 2026
17f6b09
Add module, class, and method docstrings to new files in PR #1
gaurav Mar 30, 2026
8216b0b
Fix LabeledCrossReference biolink_type fields to list[str]; simplify …
gaurav Apr 1, 2026
ac418ff
Address PR #1 review: frozen Identifier, atomic rename, fail-open HEA…
gaurav Apr 1, 2026
06cd300
Sync CLAUDE.md with current code
gaurav Apr 1, 2026
46f0863
Merge branch 'basic-implementation-in-uv' of github.com:TranslatorSRI…
gaurav Apr 1, 2026
bf1c48c
Address PR #1 review: fix six correctness and quality issues
gaurav Apr 1, 2026
49e27c0
Add --format [text|json|tsv|csv] option to all CLI commands
gaurav Apr 9, 2026
aecae50
Add console format with rich color highlighting; replace text default
gaurav Apr 9, 2026
f7cde3a
Fix Identifier.from_dict splitting string fields into characters
gaurav Apr 9, 2026
0eb061f
Deduplicate CLI options and centralise logging setup
gaurav May 17, 2026
397cc83
Simplify core query and normalisation logic
gaurav May 17, 2026
f014322
Clean up downloader: fix redundant check, remove noisy comments
gaurav May 17, 2026
6a18d10
Fix lru_cache memory leak and IN-parameter query in BabelXRefs
gaurav May 17, 2026
25bf79b
Fix urljoin silently stripping path segments in BabelDownloader
gaurav May 17, 2026
9120921
Delete unintentional src/__init__.py
gaurav May 17, 2026
dd912f1
Housekeeping: trailing newline, empty test init, FUTURE.md → issues
gaurav May 17, 2026
a4a2ed1
Apply ruff formatting to cli, formatting, and test files
gaurav May 17, 2026
a5eb3b4
Potential fix for pull request finding
gaurav May 18, 2026
a2fb574
Potential fix for pull request finding
gaurav May 18, 2026
87a05c3
Use read_parquet() inline in SQL instead of magic variable-name resol…
gaurav May 18, 2026
84bc4b1
Remove lru_cache from get_downloaded_file; fix timeout comment
gaurav May 18, 2026
66e016d
Replace lru_cache with instance-level dict caches in NodeNorm
gaurav May 18, 2026
a21d72e
Add --paths flag and depth-based coloring for recursive xrefs
gaurav May 30, 2026
7f676e4
Prefix path edge lines with "- " for GitHub markdown list paste
gaurav May 30, 2026
fcc8fd9
Use double-quoted labels in all console output (CURIE "label")
gaurav May 30, 2026
fb0f189
Omit label entirely when absent instead of showing empty placeholder
gaurav May 31, 2026
bc5c63c
Apply ruff formatting to babel_xrefs and formatting
gaurav Aug 14, 2026
effe92c
Detect the Babel release behind a server URL and pin the cache to it
gaurav Aug 14, 2026
e5a9246
Read Babel and NodeNorm URLs from .env instead of hardcoding them
gaurav Aug 14, 2026
80e90a6
Document .env configuration and Babel version handling
gaurav Aug 14, 2026
ebd4775
Configure ruff lint rules and apply the resulting fixes
gaurav Aug 14, 2026
b1d3be0
Annotate ruff failures on the PR diff and document the pre-commit step
gaurav Aug 14, 2026
cdd0d4f
Delete the unused BabelDownloader.get_output_file
gaurav Aug 14, 2026
4502c39
Cover NodeNorm version lookup, missing-file reporting and refresh sco…
gaurav Aug 14, 2026
7eaefb3
Ignore .idea/
gaurav Aug 14, 2026
65f4d4a
Remove the test data directory once all xdist workers have finished
gaurav Aug 14, 2026
da86383
Reject --paths with non-console output formats
gaurav Aug 14, 2026
fd204a6
Add --labels to the ids command
gaurav Aug 14, 2026
7c92998
Render CURIE labels through a single formatting helper
gaurav Aug 14, 2026
6ba5930
Keep tabular output valid when records omit an absent label
gaurav Aug 14, 2026
94b7f18
Batch NodeNorm lookups and Parquet scans
gaurav Aug 14, 2026
655986f
Simplify duration parsing and version caching
gaurav Aug 14, 2026
8582cfa
Ignore node_modules/
gaurav Aug 14, 2026
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
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copy this file to .env and edit as needed. Every value here can also be set as an
# environment variable, or overridden per-run by the matching command-line option.

# Public Babel release. "latest" is resolved through VERSION.txt; when the release
# behind it changes, babel-explorer refreshes the cached files automatically.
BABEL_URL=https://stars.renci.org/var/babel/latest/

# Where downloaded Babel files are cached. This holds one Babel release at a time.
# Point it somewhere per-release if you need to keep several around.
BABEL_LOCAL_DIR=data

NODENORM_URL=https://nodenormalization-sri.renci.org/

# NOTE: public Babel releases do not currently publish the DuckDB Parquet files
# (duckdb/Concord.parquet, duckdb/Identifiers.parquet) that babel-explorer needs.
# Translator team members should contact the Babel developers for the
# Translator-specific URL and set BABEL_URL to it here.
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 17 * * 2" # Tuesdays at 12pm EST (17:00 UTC); 1pm during EDT
workflow_dispatch:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --group dev
# `uv run` uses the ruff pinned in uv.lock, so CI lints with the same version
# developers have locally. --output-format github annotates the PR diff inline.
# Paths come from [tool.ruff] in pyproject.toml rather than being repeated here.
- run: uv run ruff check --output-format github
- run: uv run ruff format --check

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --group dev
- run: uv run pytest -v -m "not integration"

integration-test:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --group dev
- run: uv run pytest -v -m "integration and not slow"
14 changes: 11 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Ignore data files.
/data

# Node dependencies, wherever they are installed (e.g. web/node_modules).
# Deliberately not /web, so frontend source under it is still tracked.
node_modules/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
Expand All @@ -14,8 +21,9 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
# Python distribution lib directories (not web/src/lib/)
/lib/
/lib64/
parts/
sdist/
var/
Expand Down Expand Up @@ -173,7 +181,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/

# Abstra
# Abstra is an AI-powered process automation framework.
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
233 changes: 233 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

babel-explorer is a tool for querying and exploring Babel intermediate files. It allows users to discover why two biological/chemical identifiers are considered identical by the Babel system, which handles cross-references between different ontology and database identifiers (e.g., MONDO, HP, UMLS, HGNC).

## Development Setup

This project uses **uv** for package management:

```bash
# Install dependencies
uv sync

# Install with dev dependencies
uv sync --group dev

# Configure the Babel and NodeNorm endpoints
cp .env.example .env

# Run the CLI
uv run babel-explorer --help
```

## Configuration

`BABEL_URL`, `BABEL_LOCAL_DIR`, `BABEL_CHECK_DOWNLOAD`, `NODENORM_URL`, and
`BABEL_ALLOW_VERSION_MISMATCH` are read from `.env` (via `python-dotenv`, loaded in the `cli()`
group) or the environment. Each is also a command-line option, and precedence runs
**flag > environment variable > `.env` > built-in default**.

`.env.example` ships with the **public** Babel URL only. Public Babel releases do not currently
publish the DuckDB Parquet files this tool needs, so Translator team members must contact the
Babel developers for the Translator-specific URL and set `BABEL_URL` to it. Never commit that URL
to this repository.

## Babel versions

The Babel version behind `--babel-url` is resolved by `resolve_babel_version()`
(`core/downloader.py`), which reads `VERSION.txt` (`Babel 2026jul22`) and falls back to the final
path segment for older trees that predate it. `latest/` resolves to whatever release it currently
points at.

`BABEL_LOCAL_DIR` holds **one Babel release at a time**, recorded in a `.babel-version` marker.
When the release changes, `BabelDownloader.sync_cache_version()` deletes the `.meta` sidecars in
`<local_dir>/duckdb/` — never the Parquet files — so the existing ETag path re-checks each cached
file and re-downloads only what actually changed. This keeps `Concord.parquet` and
`Identifiers.parquet` from being read together across two different Babel releases.

`xrefs` fails when NodeNorm's `status` endpoint reports a different `babel_version` than the Babel
being queried, since labels and cliques would not match the cross-references. Pass
`--allow-version-mismatch` to proceed anyway. The check is skipped when NodeNorm is not consulted
(plain `xrefs`, `ids`) or when either version is unavailable.

## Commands

### Running the Application

```bash
# Get cross-references for one or more CURIEs
uv run babel-explorer xrefs MONDO:0004979

# Get cross-references with expansion (recursive lookup)
uv run babel-explorer xrefs MONDO:0004979 --recurse

# Get cross-references with labels from NodeNorm
uv run babel-explorer xrefs MONDO:0004979 --labels

Comment thread
gaurav marked this conversation as resolved.
# Get ID records for CURIEs
uv run babel-explorer ids MONDO:0004979

# Get ID records with labels from NodeNorm
uv run babel-explorer ids MONDO:0004979 --labels

# Test concordance changes with NodeNorm
uv run babel-explorer test-concord MONDO:0004979 HP:0000001

# Use a custom Babel server or local directory (overrides .env)
uv run babel-explorer xrefs MONDO:0004979 --local-dir data --babel-url https://stars.renci.org/var/babel/latest/
```

### Development Commands

```bash
# Run all tests (includes large file downloads)
uv run pytest -v

# Run unit tests only (fast, no network)
uv run pytest -v -m "not integration"

# Run integration tests without 2GB+ downloads
uv run pytest -v -m "integration and not slow"

# Run a single test file
uv run pytest -v tests/test_nodenorm.py
```

### Linting

**Run both of these before committing or pushing.** CI checks them on every PR, and a push
that skips them turns the PR red for reasons unrelated to the change under review.

```bash
uv run ruff check # Python lint
uv run ruff check --fix # Python auto-fix
uv run ruff format --check # Python format check
uv run ruff format # Python auto-format
```

Run them over the whole repository, not just the files you touched — `[tool.ruff]` in
`pyproject.toml` sets the scope. If `ruff format` reports files you did not edit, the repository
had drifted; commit that reformatting separately from your change so review stays readable, and
do not silently revert it.

Rules are `E`, `F`, `I` (import sorting) and `UP` (pyupgrade), with `E501` left to the formatter.
Line length is ruff's default of 88. `*.md` is excluded, because ruff 0.16+ reformats Python
inside Markdown code blocks and this repository's snippets are illustrative fragments.

## Console Output Format Conventions

### Label display

When a human-readable label is shown alongside a CURIE in console output, it always appears **immediately after the CURIE, in double quotes**:

```
MONDO:0004979 "asthma" skos:exactMatch EFO:0000270 "asthma"
```

This applies everywhere labels appear: `xrefs --labels`, `xrefs --paths --labels`, `ids --labels`, and `test-concord`.

`--paths` is console-only; combining it with `--format json`/`tsv`/`csv` is rejected up front rather
than silently emitting the full cross-reference list.

**When a label is absent, omit it entirely** — do not substitute a placeholder like `-` or `""`. A CURIE with no label renders as just the bare CURIE.

**Escaping:** embedded backslashes are escaped as `\\` and embedded double quotes as `\"`. Downstream tools can parse labels with the regex `"([^"\\]|\\.)*"`.

**Do not** use parentheses `(label)` or any other delimiter — double quotes are the sole convention.

## Architecture

### Core Components

1. **BabelDownloader** (`src/babel_explorer/core/downloader.py`):
- Downloads Babel intermediate files from a remote HTTP(S) server using Python's `requests` library (streaming downloads)
- Caches files locally in a configurable directory (default: `data/`), one Babel release at a time
- Uses `@functools.lru_cache` to avoid re-downloading
- Resolves the Babel version (`resolve_babel_version`) and refreshes the cache when it changes (`sync_cache_version`)
- Raises `MissingBabelFileError` on a 404 for a `duckdb/` file, since public releases do not publish them
- **Important**: Requires network access but no external tools like `wget`

2. **BabelXRefs** (`src/babel_explorer/core/babel_xrefs.py`):
- Main query engine for cross-references
- Uses DuckDB to query Parquet files (`Concord.parquet`, `Identifiers.parquet`)
- Supports recursive expansion of cross-references via a single `WITH RECURSIVE` query
- Uses ephemeral in-memory DuckDB connections (nothing written to disk)

3. **NodeNorm** (`src/babel_explorer/core/nodenorm.py`):
- Integration with NodeNormalization API (https://nodenormalization-sri.renci.org/)
- Fetches labels, biolink types, and equivalent identifiers for CURIEs
- Uses `@functools.lru_cache` for performance
- `get_babel_version()` reads the `status` endpoint to report which Babel release it was built from
- Optional component for label enrichment

4. **CLI** (`src/babel_explorer/cli.py`):
- Click-based command-line interface
- Three main commands: `xrefs`, `ids`, `test-concord`

### Data Flow

1. User provides CURIEs via CLI; `BABEL_URL` / `NODENORM_URL` come from `.env` or the environment
2. BabelDownloader resolves the Babel version, refreshes the cache if it changed, and ensures required Parquet files are downloaded
3. BabelXRefs queries files using DuckDB
4. If `--labels` or `--recurse` flags are set, NodeNorm is queried for additional metadata (`ids` consults NodeNorm only for `--labels`)
5. Results are printed to stdout

### Key Design Patterns

- **Lazy downloading**: Files are only downloaded when first accessed
- **LRU caching**: Heavy use of `@functools.lru_cache` to avoid redundant downloads and API calls
- **Recursive expansion**: The `--recurse` flag recursively follows all cross-references to build complete graphs
- **DuckDB for querying**: In-memory SQL queries against Parquet files for fast lookups

## Testing

### Test Structure

Tests live in `tests/` and are split into fast **unit tests** (mocked, no network) and slower **integration tests** (real downloads and API calls). Pytest markers control which tests run:

- **`@pytest.mark.integration`** — requires network access (downloads Parquet files or calls NodeNorm API)
- **`@pytest.mark.slow`** — downloads very large files (2 GB+)

Do not record per-file test counts here — they drift silently and then mislead. Get them on demand:

```bash
uv run pytest --collect-only -q -m "not integration" # unit test count
uv run pytest --collect-only -q # full count
```

**Integration tests skip when `BABEL_URL` points at a Babel release that does not publish
`duckdb/Concord.parquet`**, which is the case for every public release right now. A run reporting
a couple of dozen skips is the expected result without a Translator `BABEL_URL` in `.env`, not a
broken test environment.

### Test Infrastructure

- **`tests/conftest.py`** — Session-scoped fixtures that download Parquet files once and share them across all integration tests. The `shared_downloader` fixture HEADs `duckdb/Concord.parquet` first and skips the session on 404. Teardown removes the `data/test/` directory so the next run starts fresh.
- **`tests/constants.py`** — Shared constants (URLs, file paths) and `load_curies()` helper.
- **`tests/data/valid_curies.txt`** — One CURIE per line (`#` comments allowed). Integration tests are parametrized over this list — adding a new line automatically expands test coverage.

### Key Dataclasses

- **`Identifier`** — Frozen dataclass for a normalized NodeNorm entry (curie, label, biolink_type, taxa, description). Returned by `NodeNorm.get_identifier()` and `get_clique_identifiers()`.
- **`CrossReference`** — Frozen dataclass for Concord.parquet rows (filename, subj, pred, obj)
- **`LabeledCrossReference`** — Extends CrossReference with labels and biolink types from NodeNorm
- **`IdentifierRecord`** — Frozen dataclass for Identifiers.parquet rows (curie + dynamic extra fields). Returned by `BabelXRefs.get_curie_ids()`.

## Important Notes

- **Data directory**: The `data/` directory is gitignored and contains downloaded Parquet files and generated DuckDB databases
- **Babel versions**: The Babel release comes from whatever `--babel-url` / `BABEL_URL` points at; see [Babel versions](#babel-versions) above
- **`.env`**: gitignored. Only `.env.example` is committed, and it must never contain the Translator-specific Babel URL

## File Locations

- Source code: `src/babel_explorer/`
- Tests: `tests/`
- Test CURIEs: `tests/data/valid_curies.txt`
- Downloaded Babel files: `<BABEL_LOCAL_DIR>/duckdb/*.parquet` (default `data/duckdb/`)
- Endpoint configuration: `.env` (gitignored), template in `.env.example`
- Entry point: `src/babel_explorer/cli.py`
6 changes: 6 additions & 0 deletions FUTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Future Work

These items are tracked as GitHub issues:

- [#12](https://github.com/TranslatorSRI/babel-explorer/issues/12) — Batch NodeNorm lookups to reduce N round-trips when `--labels` is set
- [#13](https://github.com/TranslatorSRI/babel-explorer/issues/13) — Reuse a single DuckDB connection per `BabelXRefs` instance
Loading
Loading