Skip to content

Commit e989ce5

Browse files
committed
Merge branch 'development' of https://github.com/NFDI4Chem/nmrkit into development
2 parents 9aaa0c3 + be5b6d4 commit e989ce5

6 files changed

Lines changed: 628 additions & 136 deletions

File tree

app/main.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,62 @@
1313
from prometheus_fastapi_instrumentator import Instrumentator
1414
from app.schemas import HealthCheck
1515

16+
DESCRIPTION = """
17+
## NMR Kit API
18+
19+
A Python-based microservice for **storing**, **parsing**, **converting**, and
20+
**predicting** NMR (Nuclear Magnetic Resonance) spectra.
21+
22+
### Modules
23+
24+
| Module | Description |
25+
|--------|-------------|
26+
| **Chemistry** | Generate HOSE codes, label atoms via ALATIS |
27+
| **Spectra** | Parse NMR spectra from files or URLs |
28+
| **Converter** | Convert NMR raw data to NMRium JSON |
29+
| **Predict** | Predict NMR spectra using nmrdb.org or nmrshift engines |
30+
| **Registration** | Register and query molecules via lwreg |
31+
32+
### Links
33+
34+
* [Documentation](https://nfdi4chem.github.io/nmrkit)
35+
* [Source Code](https://github.com/NFDI4Chem/nmrkit)
36+
"""
37+
38+
tags_metadata = [
39+
{
40+
"name": "healthcheck",
41+
"description": "Health check endpoints to verify service availability.",
42+
},
43+
{
44+
"name": "chem",
45+
"description": "Chemistry operations including HOSE code generation and atom labeling.",
46+
},
47+
{
48+
"name": "spectra",
49+
"description": "Parse NMR spectra from uploaded files or remote URLs.",
50+
},
51+
{
52+
"name": "converter",
53+
"description": "Convert NMR raw data into NMRium-compatible JSON format.",
54+
},
55+
{
56+
"name": "predict",
57+
"description": (
58+
"Predict NMR spectra from molecular structures using "
59+
"**nmrdb.org** or **nmrshift** prediction engines."
60+
),
61+
},
62+
{
63+
"name": "registration",
64+
"description": "Register, query, and retrieve molecules using the lwreg registration system.",
65+
},
66+
]
67+
1668
app = FastAPI(
1769
title=config.PROJECT_NAME,
18-
description="Python-based microservice to store and predict spectra.",
70+
version=config.VERSION,
71+
description=DESCRIPTION,
1972
terms_of_service="https://nfdi4chem.github.io/nmrkit",
2073
contact={
2174
"name": "Steinbeck Lab",
@@ -26,6 +79,7 @@
2679
"name": "CC BY 4.0",
2780
"url": "https://creativecommons.org/licenses/by/4.0/",
2881
},
82+
openapi_tags=tags_metadata,
2983
)
3084

3185
app.include_router(registration.router)
@@ -42,6 +96,7 @@
4296
version_format="{major}",
4397
prefix_format="/v{major}",
4498
enable_latest=True,
99+
description=DESCRIPTION,
45100
terms_of_service="https://nfdi4chem.github.io/nmrkit",
46101
contact={
47102
"name": "Steinbeck Lab",
@@ -52,6 +107,7 @@
52107
"name": "CC BY 4.0",
53108
"url": "https://creativecommons.org/licenses/by/4.0/",
54109
},
110+
openapi_tags=tags_metadata,
55111
)
56112

57113
Instrumentator().instrument(app).expose(app)

app/routers/chem.py

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from typing import Annotated
22
from psycopg2.errors import UniqueViolation
33
from app.modules.cdkmodules import getCDKHOSECodes
4-
from fastapi import APIRouter, HTTPException, status, Query, Body
4+
from fastapi import APIRouter, HTTPException, status, Query
55
from app.modules.rdkitmodules import getRDKitHOSECodes
66
from app.schemas import HealthCheck
7-
from app.schemas.alatis import AlatisModel
8-
import requests
97

108
router = APIRouter(
119
prefix="/chem",
@@ -41,23 +39,91 @@ def get_health() -> HealthCheck:
4139
@router.get(
4240
"/hosecode",
4341
tags=["chem"],
44-
summary="Generates HOSE codes of molecule",
42+
summary="Generate HOSE codes for a molecule",
43+
description=(
44+
"Generate **Hierarchically Ordered Spherical Environment (HOSE)** codes "
45+
"for every atom in the given molecule. HOSE codes encode the local chemical "
46+
"environment around each atom up to a configurable number of spheres.\n\n"
47+
"Supports two cheminformatics frameworks:\n"
48+
"- **CDK** (Chemistry Development Kit) — default, supports stereo\n"
49+
"- **RDKit** — alternative implementation"
50+
),
4551
response_model=list[str],
46-
response_description="Returns an array of hose codes generated",
52+
response_description="Array of HOSE code strings, one per atom in the molecule",
4753
status_code=status.HTTP_200_OK,
54+
responses={
55+
200: {
56+
"description": "Successfully generated HOSE codes",
57+
"content": {
58+
"application/json": {
59+
"example": [
60+
"C(CC,CC,&)",
61+
"C(CC,C&,&)",
62+
"C(CC,CC,&)",
63+
"C(CCC,CC&,&)",
64+
"C(CC,CC,CC)",
65+
"C(CC,CC,CC)",
66+
]
67+
}
68+
},
69+
},
70+
409: {"description": "Molecule already exists (unique constraint violation)"},
71+
422: {"description": "Error parsing the molecular structure"},
72+
},
4873
)
4974
async def HOSE_Codes(
50-
smiles: Annotated[str, Query(examples=["CCCC1CC1"])],
51-
framework: Annotated[str, Query(enum=["cdk", "rdkit"])] = "cdk",
52-
spheres: Annotated[int, Query()] = 3,
53-
usestereo: Annotated[bool, Query()] = False,
75+
smiles: Annotated[
76+
str,
77+
Query(
78+
description="SMILES string representing the molecular structure",
79+
example="CCCC1CC1",
80+
examples=[
81+
"CCCC1CC1",
82+
"c1ccccc1",
83+
"CC(=O)O",
84+
"CCO",
85+
"C1CCCCC1",
86+
"CC(=O)Oc1ccccc1C(=O)O",
87+
],
88+
),
89+
],
90+
framework: Annotated[
91+
str,
92+
Query(
93+
enum=["cdk", "rdkit"],
94+
description="Cheminformatics framework to use for HOSE code generation",
95+
),
96+
] = "cdk",
97+
spheres: Annotated[
98+
int,
99+
Query(
100+
description="Number of spheres (bond distance) to consider around each atom",
101+
ge=1,
102+
le=10,
103+
),
104+
] = 3,
105+
usestereo: Annotated[
106+
bool,
107+
Query(
108+
description="Whether to include stereochemistry information in HOSE codes (CDK only)",
109+
),
110+
] = False,
54111
) -> list[str]:
55112
"""
56-
## Generates HOSE codes for a given molecule
57-
Endpoint to generate HOSE codes based on each atom in the given molecule.
113+
## Generate HOSE codes for a given molecule
58114
59-
Returns:
60-
HOSE Codes: An array of hose codes generated
115+
Generates HOSE (Hierarchically Ordered Spherical Environment) codes based on
116+
each atom in the given molecule. These codes are widely used in NMR chemical
117+
shift prediction.
118+
119+
### Parameters
120+
- **smiles**: A valid SMILES string (e.g. `CCCC1CC1`)
121+
- **framework**: Choose `cdk` (default) or `rdkit`
122+
- **spheres**: Number of bond spheres to encode (default: 3)
123+
- **usestereo**: Include stereochemistry in codes (CDK only, default: false)
124+
125+
### Returns
126+
An array of HOSE code strings, one for each atom in the molecule.
61127
"""
62128
try:
63129
if framework == "cdk":
@@ -78,32 +144,3 @@ async def HOSE_Codes(
78144
detail="Error parsing the structure " + e.message,
79145
headers={"X-Error": "RDKit molecule input parse error"},
80146
)
81-
82-
83-
@router.post(
84-
"/label-atoms",
85-
tags=["chem"],
86-
summary="Label atoms using ALATIS naming system",
87-
response_model=AlatisModel,
88-
response_description="",
89-
status_code=status.HTTP_200_OK,
90-
)
91-
async def label_atoms(data: Annotated[str, Body(embed=False, media_type="text/plain")]):
92-
"""
93-
## Generates atom labels for a given molecule
94-
95-
Returns:
96-
JSON with various representations
97-
"""
98-
try:
99-
url = "http://alatis.nmrfam.wisc.edu/upload"
100-
payload = {"input_text": data, "format": "format_", "response_type": "json"}
101-
response = requests.request("POST", url, data=payload)
102-
response.raise_for_status() # Raise an error for bad status codes
103-
return response.json()
104-
except Exception as e:
105-
raise HTTPException(
106-
status_code=422,
107-
detail=f"Error parsing the structure: {str(e)}",
108-
headers={"X-Error": "RDKit molecule input parse error"},
109-
)

app/routers/converter.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import subprocess
2-
from fastapi import APIRouter, HTTPException, status, Response
2+
from fastapi import APIRouter, HTTPException, status, Response, Query
33
from app.schemas import HealthCheck
44

55
router = APIRouter(
@@ -36,17 +36,42 @@ def get_health() -> HealthCheck:
3636
@router.get(
3737
"/spectra",
3838
tags=["converter"],
39-
summary="Load and convert NMR raw data",
40-
# response_model=List[int],
41-
response_description="Load and convert NMR raw data",
39+
summary="Convert NMR raw data to NMRium JSON",
40+
description=(
41+
"Fetch NMR raw data from a remote URL and convert it into "
42+
"[NMRium](https://www.nmrium.org/)-compatible JSON format. "
43+
"The conversion is performed by the **nmr-cli** tool running "
44+
"inside a Docker container.\n\n"
45+
"Supported input formats include Bruker, JCAMP-DX, and other "
46+
"formats recognized by nmr-cli."
47+
),
48+
response_description="NMRium-compatible JSON representation of the NMR data",
4249
status_code=status.HTTP_200_OK,
50+
responses={
51+
200: {
52+
"description": "Successfully converted NMR data to NMRium JSON",
53+
"content": {"application/json": {}},
54+
},
55+
500: {"description": "Conversion failed or Docker container not available"},
56+
},
4357
)
44-
async def nmr_load_save(url: str):
58+
async def nmr_load_save(
59+
url: str = Query(
60+
...,
61+
description="URL pointing to the NMR raw data file to convert",
62+
examples=["https://example.com/nmr-data/sample.zip"],
63+
),
64+
):
4565
"""
46-
## Return nmrium json
66+
## Convert NMR raw data to NMRium JSON
4767
48-
Returns:
49-
Return nmrium json
68+
Fetches NMR raw data from the provided URL and converts it into NMRium JSON format.
69+
70+
### Parameters
71+
- **url**: A publicly accessible URL pointing to the NMR raw data
72+
73+
### Returns
74+
NMRium-compatible JSON object containing the converted spectra data.
5075
"""
5176
process = subprocess.Popen(
5277
["docker exec nmr-converter nmr-cli -u " + url],

0 commit comments

Comments
 (0)