Skip to content

Commit aa2f790

Browse files
authored
Merge pull request #53 from microbiomedata/mode-and-closure-cli
Add `--mode` / `--closure` CLI; deprecate `output_directory` + `generate_reports` kwargs (closes #49)
2 parents cabcbb4 + f7055d4 commit aa2f790

13 files changed

Lines changed: 853 additions & 146 deletions

CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
### Added
10+
- `OntologyLoaderController(mode=...)` kwarg accepting `'meticulous'` (default) or `'fast-initial'`.
11+
- `meticulous` preserves 0.2.x behavior exactly: pure linkml-store, per-item upsert, force-refresh of the pystow cache, TSV reports written to `report_directory`.
12+
- `fast-initial` is the new maximum-throughput first-time-install path: raw pymongo `insert_many(ordered=False)`, no pre-read, no upsert, no report tracking, no TSV writes. Reuses the pystow cache if present.
13+
- `OntologyLoaderController(closure=...)` kwarg accepting a string or list. Values: `combined` (default), `isa`, `partof`, `all` (exclusive shorthand for combined+isa+partof), `none` (exclusive — emit no ancestry closure).
14+
- `OntologyLoaderController(report_directory=...)` kwarg — renamed from `output_directory`.
15+
- `OntologyLoaderController(source_ontology=...)` now accepts a list of strings as well as a single string. Multiple ontologies are processed sequentially in the given order; failure on one halts the run.
16+
- New CLI surface — four flags total: `--source-ontology` (repeatable, required), `--report-directory`, `--mode`, `--closure` (repeatable).
17+
- `MongoDBLoader.insert_ontology_data_fast_initial(...)` — the raw-pymongo write method used by `mode='fast-initial'`.
18+
- `OntologyProcessor(force_refresh=...)` constructor kwarg — `True` (default) preserves 0.2.x cache-wiping behavior; `False` reuses the cached pystow artifact when present.
19+
20+
### Changed
21+
- `OntologyProcessor.get_relations_closure()` signature: `predicates=` removed in favor of `closure=` (string or list of strings). The old hardcoded ancestry-relation name (`entailed_isa_partof_closure`) is now selected per-closure: `entailed_isa_partof_closure`, `entailed_isa_closure`, or `entailed_partof_closure`.
22+
- CLI no longer takes `--generate-reports`. Equivalents under the new design:
23+
- Old `--generate-reports true` (the implicit default) → new `--mode meticulous` (the default).
24+
- Old `--generate-reports false` → new `--mode fast-initial`.
25+
26+
### Deprecated
27+
- `OntologyLoaderController(output_directory=...)` — use `report_directory=` instead. The old kwarg is an alias and emits `DeprecationWarning`. Passing both raises `ValueError`.
28+
- `OntologyLoaderController(generate_reports=True)` — no-op with `DeprecationWarning`. (True was always the default.)
29+
- `OntologyLoaderController(generate_reports=False)` — maps to `mode='fast-initial'` with `DeprecationWarning`. If `mode` was also passed and isn't `'meticulous'`, raises `ValueError`.
30+
31+
### Removed
32+
- (nothing removed in this release — all 0.2.x kwargs continue to work as deprecated aliases. Removal slated for the next major release after downstream callers — notably the `nmdc-runtime` Dagster job — migrate.)
33+
34+
### Migration
35+
36+
The exact 0.2.x call site in `nmdc-runtime`'s Dagster op (`nmdc_runtime/site/ops.py`, `load_ontology`) — `OntologyLoaderController(source_ontology=str, output_directory=str, generate_reports=True, mongo_client=..., db_name=...)` — runs **unchanged** under this release. Two `DeprecationWarning` lines appear in the Dagster logs as a nudge to update.
37+
38+
To migrate when convenient:
39+
40+
```python
41+
# 0.2.x
42+
OntologyLoaderController(
43+
source_ontology="envo",
44+
output_directory="/tmp/ontology_reports",
45+
generate_reports=True,
46+
mongo_client=client,
47+
db_name="nmdc",
48+
)
49+
50+
# 0.3.0+
51+
OntologyLoaderController(
52+
source_ontology="envo", # or ["envo", "po", "uberon"]
53+
report_directory="/tmp/ontology_reports",
54+
mode="meticulous", # default; or "fast-initial"
55+
closure="combined", # default; or "isa", "partof", "all", "none", or a list
56+
mongo_client=client,
57+
db_name="nmdc",
58+
)
59+
```

README.md

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,36 @@ The Docker container networking uses container names (like 'mongo') for internal
7575
```
7676
7777
#### Command line
78+
7879
```bash
7980
% poetry install
8081
% poetry run ontology_loader --help
81-
% poetry run ontology_loader --source-ontology "envo"
82-
% poetry run ontology_loader --source-ontology "uberon"
82+
% poetry run ontology_loader --source-ontology envo
83+
% poetry run ontology_loader --source-ontology envo --source-ontology po --source-ontology uberon
8384
```
8485
86+
Four flags:
87+
88+
- `--source-ontology <name>` — required, repeatable. Lowercase prefix (envo, po, uberon, ncbitaxon, …). Multiple ontologies are processed sequentially in the given order.
89+
- `--report-directory <dir>` — TSV report destination (only used in `meticulous` mode). Defaults to a fresh temp directory.
90+
- `--mode {meticulous|fast-initial}` — default `meticulous`. See "Modes" below.
91+
- `--closure {combined|isa|partof|all|none}` — default `combined`. Repeatable; values combine. `all` and `none` are exclusive.
92+
93+
##### Modes
94+
95+
- **`meticulous`** (default): Preserves 0.2.x behavior — pure linkml-store, per-item upsert, force-refresh of the pystow cache on every run, TSV reports (`ontology_updates.tsv`, `ontology_inserts.tsv`, `ontology_relation_inserts.tsv`) written to `--report-directory`. Use this for incremental updates of an already-loaded ontology.
96+
- **`fast-initial`**: Maximum-throughput first-time install. Raw pymongo `insert_many(ordered=False)`, no upsert, no pre-read, no report tracking, no TSV writes. Reuses the pystow cache if present (downloads only when missing). Use this when the target collections are empty or duplicate-key errors are acceptable. Expected ~3-5x faster than `meticulous` on large ontologies (e.g. NCBITaxon's 2.7M classes + 54.7M relations).
97+
98+
##### Closure shorthands
99+
100+
- `--closure combined` (default): emits `entailed_isa_partof_closure` (rdfs:subClassOf ∪ BFO:0000050).
101+
- `--closure isa`: emits `entailed_isa_closure` (rdfs:subClassOf only).
102+
- `--closure partof`: emits `entailed_partof_closure` (BFO:0000050 only).
103+
- `--closure all`: shorthand for `--closure combined --closure isa --closure partof`. Exclusive.
104+
- `--closure none`: emit no ancestry closure, only direct relationships. Exclusive.
105+
106+
Repeat the flag to combine specific closures: `--closure isa --closure partof` emits both `entailed_isa_closure` and `entailed_partof_closure`.
107+
85108
#### Running the tests
86109
```bash
87110
% make test
@@ -92,7 +115,8 @@ The Docker container networking uses container names (like 'mongo') for internal
92115
% make lint
93116
```
94117
95-
#### Python example usage
118+
#### Python API
119+
96120
```bash
97121
pip install nmdc-ontology-loader
98122
```
@@ -101,48 +125,68 @@ pip install nmdc-ontology-loader
101125
from ontology_loader.ontology_load_controller import OntologyLoaderController
102126
import tempfile
103127
104-
def load_ontology():
105-
"""Load an ontology using the default MongoDB connection."""
106-
loader = OntologyLoaderController(
107-
source_ontology="envo",
108-
output_directory=tempfile.gettempdir(),
109-
generate_reports=True,
110-
)
111-
loader.run_ontology_loader()
128+
# Default: pure linkml-store + TSV reports (preserves 0.2.x behavior)
129+
OntologyLoaderController(
130+
source_ontology="envo", # str or list[str]
131+
report_directory=tempfile.gettempdir(), # only used in 'meticulous' mode
132+
mode="meticulous", # or 'fast-initial'
133+
closure="combined", # str or list[str]
134+
).run_ontology_loader()
112135
```
113136
114-
#### Using with an existing MongoDB connection
137+
##### Fast first-time install of a large ontology
115138
116-
If you already have a MongoDB connection established (e.g., in a Dagster/Dagit job), you can pass it directly to the OntologyLoaderController:
139+
```python
140+
OntologyLoaderController(
141+
source_ontology="ncbitaxon",
142+
mode="fast-initial", # raw pymongo insert_many, no upsert, no reports
143+
closure="isa", # is_a only; combined closure is too large for NCBITaxon
144+
).run_ontology_loader()
145+
```
146+
147+
##### Multiple ontologies in one invocation
148+
149+
```python
150+
OntologyLoaderController(
151+
source_ontology=["envo", "po", "uberon"], # processed sequentially in given order
152+
mode="meticulous",
153+
).run_ontology_loader()
154+
```
155+
156+
##### Using with an existing MongoDB connection
157+
158+
If you already have a MongoDB connection (e.g., in a Dagster/Dagit job), pass it directly:
117159
118160
```python
119161
from pymongo import MongoClient
120162
from ontology_loader.ontology_load_controller import OntologyLoaderController
121-
import tempfile
122163
123-
# Use an existing MongoDB client
124164
mongo_client = MongoClient("mongodb://admin:password@localhost:27018/nmdc?authSource=admin")
125165
126-
# Pass the client and database name to OntologyLoaderController
127-
loader = OntologyLoaderController(
166+
OntologyLoaderController(
128167
source_ontology="envo",
129-
output_directory=tempfile.gettempdir(),
130-
generate_reports=True,
131-
mongo_client=mongo_client, # Pass the existing client
132-
db_name="nmdc", # Required when passing an existing client
133-
)
134-
135-
# The loader will use the provided client instead of creating a new connection
136-
loader.run_ontology_loader()
168+
mode="meticulous",
169+
mongo_client=mongo_client, # Pass the existing client
170+
db_name="nmdc", # Required when passing an existing client
171+
).run_ontology_loader()
137172
```
138173
139-
This approach is particularly useful when:
140-
- You're running in a job scheduler like Dagster/Dagit
141-
- You want to reuse an existing connection pool
142-
- You have custom MongoDB connection settings that are managed externally
143-
- You need to use a connection with specific authentication or configuration
174+
> **Note**: When passing an existing MongoDB client, you must also provide `db_name`. The database name cannot be auto-determined from a MongoClient instance.
175+
176+
#### Migrating from 0.2.x
177+
178+
The 0.2.x constructor signature (`source_ontology`, `output_directory`, `generate_reports`, `mongo_client`, `db_name`) continues to work as deprecated aliases. The exact call site in nmdc-runtime's Dagster `load_ontology` op runs unchanged under 0.3.0; two `DeprecationWarning` lines appear in the logs as a nudge.
179+
180+
| old kwarg | new kwarg | behavior |
181+
|---|---|---|
182+
| `source_ontology=<str>` | `source_ontology=<str \| list[str]>` | unchanged; now also accepts a list |
183+
| `output_directory=<str>` | `report_directory=<str>` | renamed; old kwarg works as alias with `DeprecationWarning`. Passing both raises. |
184+
| `generate_reports=True` | (gone — implicit) | no-op with `DeprecationWarning` (True was always the default) |
185+
| `generate_reports=False` | `mode='fast-initial'` | mapped with `DeprecationWarning`. If `mode` was also passed and isn't `'meticulous'`, raises. |
186+
| (none) | `mode='meticulous'` (default) | new; default preserves 0.2.x write path |
187+
| (none) | `closure='combined'` (default) | new; default preserves 0.2.x ancestry behavior |
144188
145-
> **Note**: When passing an existing MongoDB client, you must also provide the `db_name` parameter to specify which database to use. This is required as the database name cannot be automatically determined from a MongoDB client instance.
189+
See `CHANGELOG.md` for the full release note and a side-by-side migration code sample.
146190
147191
### Testing CRUD operations in a live MongoDB
148192

src/ontology_loader/cli.py

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,84 @@
1-
"""Cli methods for ontology loading from the command line."""
1+
"""Command-line interface for the ontology loader."""
22

33
import logging
44

55
import click
66

7-
from ontology_loader.ontology_load_controller import OntologyLoaderController
7+
from ontology_loader.ontology_load_controller import VALID_MODES, OntologyLoaderController
8+
from ontology_loader.ontology_processor import VALID_CLOSURES
89

910
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
1011
logger = logging.getLogger(__name__)
1112

1213

1314
@click.command()
14-
@click.option("--source-ontology", default="envo", help="Lowercase ontology prefix, e.g., envo, go, uberon, etc.")
15-
@click.option("--output-directory", default=None, help="Output directory for reporting, default is /tmp")
16-
@click.option("--generate-reports", default=True, help="Generate reports")
17-
def cli(source_ontology, output_directory, generate_reports):
18-
"""
19-
CLI entry point for the ontology loader.
15+
@click.option(
16+
"--source-ontology",
17+
"source_ontologies",
18+
multiple=True,
19+
required=True,
20+
help=(
21+
"Lowercase ontology prefix (e.g., envo, po, uberon, ncbitaxon). "
22+
"Pass repeatedly to load multiple ontologies in one invocation: "
23+
"--source-ontology envo --source-ontology po --source-ontology uberon. "
24+
"Processed sequentially in the given order; fail-fast on per-ontology error."
25+
),
26+
)
27+
@click.option(
28+
"--report-directory",
29+
default=None,
30+
help=("Directory for TSV reports (only used when --mode=meticulous). " "Defaults to the system temp directory."),
31+
)
32+
@click.option(
33+
"--mode",
34+
type=click.Choice(VALID_MODES, case_sensitive=False),
35+
default="meticulous",
36+
show_default=True,
37+
help=(
38+
"meticulous: pure linkml-store, per-item upsert, TSV reports; matches 0.2.x behavior. "
39+
"fast-initial: raw pymongo insert_many, no upsert, no reports; for first-time installs "
40+
"of large ontologies."
41+
),
42+
)
43+
@click.option(
44+
"--closure",
45+
"closures",
46+
type=click.Choice(VALID_CLOSURES, case_sensitive=False),
47+
multiple=True,
48+
default=("combined",),
49+
show_default=True,
50+
help=(
51+
"Which ancestry closures to emit. Repeatable; values combine. "
52+
"combined = rdfs:subClassOf + BFO:0000050 (entailed_isa_partof_closure); "
53+
"isa = rdfs:subClassOf only; partof = BFO:0000050 only; "
54+
"all = shorthand for combined + isa + partof (exclusive); "
55+
"none = no ancestry closure, direct relationships only (exclusive). "
56+
"Example: --closure isa --closure partof emits both."
57+
),
58+
)
59+
def cli(source_ontologies, report_directory, mode, closures):
60+
r"""
61+
Load one or more ontologies into MongoDB.
62+
63+
Set MongoDB connection details via MONGO_HOST, MONGO_PORT, MONGO_USERNAME, MONGO_PASSWORD, MONGO_DB.
2064
21-
:param source_ontology: Lowercase ontology prefix, e.g., envo, go, uberon, etc.
22-
:param output_directory: Output directory for reporting, default is /tmp
23-
:param generate_reports: Generate reports or not, default is True
65+
Behavior contract:
2466
25-
Set the parameters for the connection to mongodb in the environment variables MONGO_HOST, MONGO_PORT,
26-
MONGO_USER, MONGO_PASSWORD, MONGO_DB.
67+
\b
68+
- --mode meticulous (default): preserves Sierra/0.2.x behavior — pure linkml-store
69+
per-item upsert, force-refresh of the pystow cache, TSV reports written to
70+
--report-directory.
71+
- --mode fast-initial: maximum-throughput first-time install — raw pymongo
72+
insert_many, no upsert, no reporting, reuses the pystow cache if present.
2773
"""
28-
logger.info(f"Processing ontology: {source_ontology}")
74+
closure_arg = list(closures)
75+
logger.info(f"Processing ontologies: {list(source_ontologies)} (mode={mode}, closure={closure_arg})")
2976

30-
# Initialize the MongoDB Loader
3177
loader = OntologyLoaderController(
32-
source_ontology=source_ontology,
33-
output_directory=output_directory,
34-
generate_reports=generate_reports,
78+
source_ontology=list(source_ontologies),
79+
report_directory=report_directory,
80+
mode=mode,
81+
closure=closure_arg,
3582
)
3683
loader.run_ontology_loader()
3784

src/ontology_loader/mongo_db_config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def set_existing_client(self, client):
6060
When set, this client will be used instead of creating a new connection.
6161
6262
Args:
63+
----
6364
client: An existing pymongo.MongoClient instance
6465
6566
"""
@@ -69,7 +70,8 @@ def has_existing_client(self):
6970
"""
7071
Check if an existing MongoDB client has been set.
7172
72-
Returns:
73+
Returns
74+
-------
7375
bool: True if an existing client is available, False otherwise
7476
7577
"""

0 commit comments

Comments
 (0)