Skip to content

Commit bcbc032

Browse files
refactor: stream nmr-cli output as JSON response in parse-spectra endpoint
1 parent 87b3bb9 commit bcbc032

File tree

1 file changed

+36
-29
lines changed

1 file changed

+36
-29
lines changed

app/routers/spectra.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class UrlParseRequest(BaseModel):
4848
False,
4949
description="Enable ranges and zones automatic detection",
5050
)
51+
raw_data: bool = Field(
52+
False, description="Include raw data in the output (default: data source)")
5153

5254
model_config = {
5355
"json_schema_extra": {
@@ -92,8 +94,9 @@ def run_command(
9294
capture_snapshot: bool = False,
9395
auto_processing: bool = False,
9496
auto_detection: bool = False,
95-
) -> dict:
96-
"""Execute nmr-cli command in Docker container"""
97+
raw_data: bool = False,
98+
) -> StreamingResponse:
99+
"""Execute nmr-cli parse-spectra command in Docker container."""
97100

98101
cmd = ["nmr-cli", "parse-spectra"]
99102

@@ -108,41 +111,33 @@ def run_command(
108111
cmd.append("-p")
109112
if auto_detection:
110113
cmd.append("-d")
114+
if raw_data:
115+
cmd.append("-r")
111116

112117
try:
113118
result = subprocess.run(
114119
["docker", "exec", NMR_CLI_CONTAINER] + cmd,
115120
capture_output=True,
116-
text=False,
117-
timeout=120
121+
timeout=120,
118122
)
119123
except subprocess.TimeoutExpired:
120124
raise HTTPException(
121-
status_code=408,
122-
detail="Processing timeout exceeded"
123-
)
125+
status_code=408, detail="Processing timeout exceeded")
124126
except FileNotFoundError:
125127
raise HTTPException(
126-
status_code=500,
127-
detail="Docker not found or nmr-converter container not running."
128-
)
128+
status_code=500, detail="Docker not found or nmr-converter container not running.")
129129

130130
if result.returncode != 0:
131-
error_msg = result.stderr.decode(
132-
"utf-8") if result.stderr else "Unknown error"
133131
raise HTTPException(
134132
status_code=422,
135-
detail=f"NMR CLI error: {error_msg}"
133+
detail=f"NMR CLI error: {result.stderr.decode('utf-8') or 'Unknown error'}",
136134
)
137135

138-
# Parse output
139-
try:
140-
return json.loads(result.stdout.decode("utf-8"))
141-
except json.JSONDecodeError as e:
142-
raise HTTPException(
143-
status_code=500,
144-
detail=f"Invalid JSON from NMR CLI: {e}"
145-
)
136+
return StreamingResponse(
137+
io.BytesIO(result.stdout),
138+
media_type="application/json",
139+
headers={"Content-Disposition": "attachment; filename=parse-output.json"},
140+
)
146141

147142

148143
def run_publication_string_command(publication_string: str) -> dict:
@@ -229,16 +224,20 @@ def remove_file_from_container(container_path: str) -> None:
229224
class PeakItem(BaseModel):
230225
"""A single NMR peak."""
231226
x: float = Field(..., description="Chemical shift in ppm")
232-
y: Optional[float] = Field(1.0, description="Peak intensity (default: 1.0)")
233-
width: Optional[float] = Field(1.0, description="Peak width in Hz (default: 1.0)")
227+
y: Optional[float] = Field(
228+
1.0, description="Peak intensity (default: 1.0)")
229+
width: Optional[float] = Field(
230+
1.0, description="Peak width in Hz (default: 1.0)")
234231

235232

236233
class PeaksToNMRiumOptions(BaseModel):
237234
"""Options for peaks-to-NMRium conversion."""
238-
nucleus: Optional[str] = Field("1H", description="Nucleus type (e.g. '1H', '13C')")
235+
nucleus: Optional[str] = Field(
236+
"1H", description="Nucleus type (e.g. '1H', '13C')")
239237
solvent: Optional[str] = Field("", description="NMR solvent")
240238
frequency: Optional[float] = Field(400, description="NMR frequency in MHz")
241-
nbPoints: Optional[int] = Field(131072, description="Number of points for spectrum generation", alias="nb_points")
239+
nbPoints: Optional[int] = Field(
240+
131072, description="Number of points for spectrum generation", alias="nb_points")
242241

243242
model_config = {"populate_by_name": True}
244243

@@ -276,7 +275,8 @@ class PeaksToNMRiumRequest(BaseModel):
276275
def run_peaks_to_nmrium_command(payload: dict) -> str:
277276
"""Execute nmr-cli peaks-to-nmrium command in Docker container via stdin."""
278277

279-
cmd = ["docker", "exec", "-i", NMR_CLI_CONTAINER, "nmr-cli", "peaks-to-nmrium"]
278+
cmd = ["docker", "exec", "-i", NMR_CLI_CONTAINER,
279+
"nmr-cli", "peaks-to-nmrium"]
280280
stdin_data = json.dumps(payload)
281281

282282
try:
@@ -298,7 +298,8 @@ def run_peaks_to_nmrium_command(payload: dict) -> str:
298298
)
299299

300300
if result.returncode != 0:
301-
error_msg = result.stderr.decode("utf-8") if result.stderr else "Unknown error"
301+
error_msg = result.stderr.decode(
302+
"utf-8") if result.stderr else "Unknown error"
302303
raise HTTPException(
303304
status_code=422,
304305
detail=f"NMR CLI error: {error_msg}",
@@ -344,7 +345,8 @@ def run_peaks_to_nmrium_command(payload: dict) -> str:
344345
},
345346
)
346347
async def parse_spectra_from_file(
347-
file: UploadFile = File(..., description="NMR spectra file to parse (JCAMP-DX, Bruker zip, etc.)"),
348+
file: UploadFile = File(
349+
..., description="NMR spectra file to parse (JCAMP-DX, Bruker zip, etc.)"),
348350
capture_snapshot: bool = Form(
349351
False,
350352
description="Generate an image snapshot of the spectra",
@@ -357,6 +359,8 @@ async def parse_spectra_from_file(
357359
False,
358360
description="Enable ranges and zones automatic detection",
359361
),
362+
raw_data: bool = Form(
363+
False, description="Include raw data in the output (default: data source references)")
360364
):
361365
"""
362366
## Parse spectra from an uploaded file
@@ -369,7 +373,7 @@ async def parse_spectra_from_file(
369373
| `capture_snapshot` | Capture an image snapshot of the spectra |
370374
| `auto_processing` | Automatically process FID → FT spectra |
371375
| `auto_detection` | Automatically detect ranges and zones |
372-
376+
| `raw_data` | Include raw data in the output (default: data source) |
373377
### Returns
374378
Parsed spectra data in NMRium-compatible JSON format.
375379
"""
@@ -398,6 +402,7 @@ async def parse_spectra_from_file(
398402
capture_snapshot=capture_snapshot,
399403
auto_processing=auto_processing,
400404
auto_detection=auto_detection,
405+
raw_data=raw_data,
401406
)
402407

403408
except HTTPException:
@@ -445,6 +450,7 @@ async def parse_spectra_from_url(request: UrlParseRequest):
445450
| `capture_snapshot` | Capture an image snapshot of the spectra |
446451
| `auto_processing` | Automatically process FID → FT spectra |
447452
| `auto_detection` | Automatically detect ranges and zones |
453+
| `raw_data` | Include raw data in the output (default: data source) |
448454
449455
### Returns
450456
Parsed spectra data in NMRium-compatible JSON format.
@@ -455,6 +461,7 @@ async def parse_spectra_from_url(request: UrlParseRequest):
455461
capture_snapshot=request.capture_snapshot,
456462
auto_processing=request.auto_processing,
457463
auto_detection=request.auto_detection,
464+
raw_data=request.raw_data,
458465
)
459466

460467
except HTTPException:

0 commit comments

Comments
 (0)