Skip to content

Commit 40bbf4b

Browse files
committed
feat: CLI for analysis and API
1 parent 831d941 commit 40bbf4b

3 files changed

Lines changed: 172 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@ dependencies = [
2020
"rdflib>=7.1.4",
2121
"SPARQLWrapper>=2.0.0",
2222
"rapidfuzz>=3.0.0",
23+
"typer>=0.12.5",
2324
]
2425
[dependency-groups]
2526
dev = [
2627
"notebook>=7.3.2",
2728
]
2829

2930
[project.scripts]
30-
cli = 'strings2things.cli:main'
31+
# Primary multi-command CLI (Typer based)
32+
strings2things = 'strings2things.cli:app'
33+
# Shortcut to launch API server directly
34+
strings2things-serve = 'strings2things.cli:serve'
35+
# Backward compatibility alias if previous users relied on `cli`
36+
cli = 'strings2things.cli:app'
3137

3238
[build-system]
3339
requires = ["hatchling"]

src/strings2things/cli.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Command Line Interface for strings2things.
2+
3+
Provides:
4+
- `strings2things serve` : Run the FastAPI application (uvicorn)
5+
- `strings2things transform` : Transform an RDF input file (Turtle or JSON-LD)
6+
7+
Installation (editable):
8+
uv pip install -e .
9+
10+
Examples:
11+
strings2things serve --host 0.0.0.0 --port 8080
12+
strings2things transform --input data.ttl --serialization jsonld --output out.jsonld
13+
14+
Environment:
15+
Ontology loading relies on environment variables (see config). Use `--no-ontology`
16+
to run transformation without loading ontologies.
17+
"""
18+
from __future__ import annotations
19+
20+
import json
21+
import sys
22+
import typer
23+
import uvicorn
24+
from pathlib import Path
25+
from typing import Optional, Literal
26+
27+
from strings2things.app.core.ontology_manager import OntologyManager
28+
from strings2things.app.core.rdf_transformer import RDFTransformer
29+
from strings2things.app.utils.rdf_utils import parse_rdf, serialize_rdf
30+
31+
app = typer.Typer(help="CLI tools for the strings2things RDF transformer")
32+
33+
34+
def _load_label_map(disable: bool) -> dict[str, str]:
35+
if disable:
36+
return {}
37+
manager = OntologyManager()
38+
try:
39+
manager.load_ontologies()
40+
except Exception as e: # pragma: no cover - resilience path
41+
typer.echo(f"[warn] Ontology load failed: {e}", err=True)
42+
return {}
43+
return manager.get_label_map() or {}
44+
45+
46+
@app.command()
47+
def serve(
48+
host: str = typer.Option("127.0.0.1", help="Host interface"),
49+
port: int = typer.Option(8000, help="Port to bind"),
50+
reload: bool = typer.Option(False, help="Enable uvicorn reload (dev only)"),
51+
log_level: str = typer.Option("info", help="Uvicorn log level"),
52+
):
53+
"""Run the FastAPI application via uvicorn."""
54+
uvicorn.run(
55+
"strings2things.app.main:app",
56+
host=host,
57+
port=port,
58+
reload=reload,
59+
log_level=log_level,
60+
)
61+
62+
63+
@app.command()
64+
def transform(
65+
input: Path = typer.Option(..., exists=True, dir_okay=False, readable=True, help="Input RDF file (Turtle or JSON-LD)"),
66+
serialization: Literal["jsonld", "turtle"] = typer.Option(
67+
..., "--serialization", "-s", help="Output serialization format"
68+
),
69+
output: Optional[Path] = typer.Option(
70+
None, "--output", "-o", help="Output file path (stdout if omitted)"
71+
),
72+
fuzzy: bool = typer.Option(False, help="Enable fuzzy label matching"),
73+
fuzzy_threshold: int = typer.Option(90, min=0, max=100, help="Fuzzy threshold 0-100"),
74+
no_ontology: bool = typer.Option(
75+
False, help="Disable ontology loading (use empty label map)"
76+
),
77+
):
78+
"""Transform an RDF graph from a local file and emit the result."""
79+
data = input.read_bytes()
80+
# Simple format inference
81+
first = data.lstrip()[:1]
82+
if first in (b"{", b"["):
83+
in_format = "json-ld"
84+
else:
85+
in_format = "turtle"
86+
87+
graph = parse_rdf(data, format=in_format)
88+
label_map = _load_label_map(disable=no_ontology)
89+
transformer = RDFTransformer(label_map, fuzzy=fuzzy, fuzzy_threshold=fuzzy_threshold)
90+
transformed = transformer.transform(graph)
91+
out_fmt = "json-ld" if serialization == "jsonld" else "turtle"
92+
serialized = serialize_rdf(transformed, output_format=out_fmt)
93+
94+
if output:
95+
output.write_text(serialized, encoding="utf-8")
96+
else:
97+
typer.echo(serialized)
98+
99+
100+
def main(): # legacy entry point if needed
101+
app()
102+
103+
if __name__ == "__main__": # pragma: no cover
104+
main()

uv.lock

Lines changed: 61 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)