diff --git a/api/http/.pre-commit-config.yaml b/api/http/.pre-commit-config.yaml index 1fa3a112e2..38b2ed0244 100644 --- a/api/http/.pre-commit-config.yaml +++ b/api/http/.pre-commit-config.yaml @@ -29,8 +29,15 @@ repos: language: system types: [ python ] - -- repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.910" +- repo: local hooks: - id: mypy + name: mypy + language: system + entry: mypy + args: + [ + "--config-file", + "api/http/mypy.ini" + ] + types: [ python ] diff --git a/api/http/Dockerfile b/api/http/Dockerfile index 4b8985d078..305f93eb61 100644 --- a/api/http/Dockerfile +++ b/api/http/Dockerfile @@ -16,7 +16,7 @@ # limitations under the License. # For test purposes -FROM python:3.9-slim-buster as indigo_service_dev +FROM python:3.10-slim-buster as indigo_service_dev RUN mkdir -p /opt/indigo WORKDIR /opt/indigo diff --git a/api/http/indigo_service/indigo_http.py b/api/http/indigo_service/indigo_http.py index 5cfb8c8ce0..89d9a6f31a 100644 --- a/api/http/indigo_service/indigo_http.py +++ b/api/http/indigo_service/indigo_http.py @@ -60,6 +60,7 @@ def compounds( jsonapi.ValidationRequest, jsonapi.CompoundConvertRequest, jsonapi.RenderRequest, + jsonapi.PKARequest, ], ) -> List[Tuple[str, jsonapi.CompoundFormat]]: return service.extract_pairs(request.data.attributes.compound) @@ -190,6 +191,27 @@ def descriptors( return jsonapi.make_descriptor_response(results) +@app.post(f"{BASE_URL_INDIGO}/pka", response_model=jsonapi.PKAResponse) +def pka(request: jsonapi.PKARequest) -> jsonapi.PKAResponse: + compound, *_ = service.extract_compounds(compounds(request)) + if request.data.attributes.pka_model == jsonapi.PKAModel.ADVANCED: + indigo().setOption("pKa-model", jsonapi.PKAModel.ADVANCED.value) + pka_model_level = request.data.attributes.pka_model_level + pka_model_min_level = request.data.attributes.pka_model_min_level + pka_values = jsonapi.AtomToValueContainer() + for atom in compound.iterateAtoms(): + pka_values.mappings.append( + jsonapi.AtomToValueMapping( + index=atom.index(), + symbol=atom.symbol(), + value=compound.getAcidPkaValue( + atom, pka_model_level, pka_model_min_level + ), + ) + ) + return jsonapi.make_pka_response(pka_values) + + @app.post( f"{BASE_URL_INDIGO}/commonBits", response_model=jsonapi.CommonBitsResponse, @@ -209,9 +231,7 @@ def common_bits( @app.post(f"{BASE_URL_INDIGO}/render", response_model=jsonapi.RenderResponse) -def render( - request: jsonapi.RenderRequest, -) -> jsonapi.RenderResponse: +def render(request: jsonapi.RenderRequest,) -> jsonapi.RenderResponse: compound, *_ = service.extract_compounds(compounds(request)) output_format = request.data.attributes.outputFormat indigo_renderer = IndigoRenderer(indigo()) @@ -245,58 +265,3 @@ def run_debug() -> None: if __name__ == "__main__": run_debug() - - -# TODO: /indigo/render with alternative responses types -# @app.post(f"{BASE_URL_INDIGO}/render") -# def render( -# request: jsonapi.RenderRequest, -# ) -> Union[Response, FileResponse]: -# compound, *_ = service.extract_compounds(compounds(request)) -# output_format = request.data.attributes.outputFormat -# indigo_renderer = IndigoRenderer(indigo()) -# indigo().setOption( -# "render-output-format", jsonapi.rendering_formats.get(output_format) -# ) -# options = request.data.attributes.options -# if options: -# for option, value in options.items(): -# if option == "render-output-format": -# raise HTTPException( -# status_code=400, detail="Choose only one output format" -# ) -# indigo().setOption(option, value) -# if output_format == "image/png": -# result = indigo_renderer.renderToBuffer(compound).tobytes() -# response = Response( -# result, -# headers={"Content-Type": "image/png"} -# ) -# elif output_format == "image/png;base64": -# result = indigo_renderer.renderToBuffer(compound).tobytes() -# decoded_image = base64.b64encode(result).decode("utf-8") -# image_base64 = f"data:image/png;base64,{decoded_image}" -# response = Response( -# image_base64, -# headers={"Content-Type": "image/png"} -# ) -# elif output_format == "image/svg+xml": -# result = indigo_renderer.renderToString(compound) -# response = Response( -# result, -# headers={"Content-Type": "image/svg+xml"} -# ) -# elif output_format == "application/pdf": -# result = indigo_renderer.renderToBuffer(compound).tobytes() -# response = Response( -# result, headers={ -# "Content-Type": "application/pdf", -# "Content-Disposition": "attachment; filename=mol.pdf" -# } -# ) -# else: -# raise HTTPException( -# status_code=400, -# detail=f"Incorrect output format {output_format}" -# ) -# return response diff --git a/api/http/indigo_service/jsonapi.py b/api/http/indigo_service/jsonapi.py index 2a78df4f56..e441c9192f 100644 --- a/api/http/indigo_service/jsonapi.py +++ b/api/http/indigo_service/jsonapi.py @@ -515,8 +515,6 @@ class Descriptors(str, Enum): MONOISOTOPIC_MASS = "monoisotopicMass" MOST_ABUNDANT_MASS = "mostAbundantMass" NAME = "name" - GET_ACID_PKA_VALUE = "acidPkaValue" - GET_BASIC_PKA_VALUE = "basicPkaValue" GROSS_FORMULA = "grossFormula" @@ -563,8 +561,6 @@ class DescriptorResultModel(BaseModel): monoisotopicMass: Optional[str] mostAbundantMass: Optional[str] name: Optional[str] - getAcidPkaValue: Optional[str] - getBasicPkaValue: Optional[str] grossFormula: Optional[str] @@ -585,6 +581,55 @@ def make_descriptor_response( ] +# PKA models + + +class PKAType(str, Enum): + ACID = "acid" + BASIC = "basic" + + +class PKAModel(str, Enum): + SIMPLE = "simple" + ADVANCED = "advanced" + + +class PKARequestModel(BaseModel): + compound: CompoundObject + pka_model: PKAModel + pka_type: PKAType = PKAType.ACID + pka_model_level: int = 0 + pka_model_min_level: int = 0 + + +class PKARequestModelType(BaseModel): + __root__ = "pka" + + +class AtomToValueMapping(BaseModel): + index: int + symbol: str + value: float + + +class AtomToValueContainer(BaseModel): + mappings: list[AtomToValueMapping] = [] + + +class PKAResultModelType(BaseModel): + __root__ = "pkaResult" + + +PKARequest = Request[PKARequestModelType, PKARequestModel] +PKAResponse = Response[PKAResultModelType, AtomToValueContainer] + + +def make_pka_response(mappings: AtomToValueContainer) -> PKAResponse: + return PKAResponse( + **{"data": {"type": "pkaResult", "attributes": mappings}} + ) + + # Render @@ -618,8 +663,7 @@ class RenderResultModel(BaseModel): def make_render_response( - raw_image: bytes, - output_format: str, + raw_image: bytes, output_format: str ) -> RenderResponse: if output_format == "image/svg+xml": str_image = raw_image.decode("utf-8") diff --git a/api/http/mypy.ini b/api/http/mypy.ini index 072fef58ff..7f90aadf6b 100644 --- a/api/http/mypy.ini +++ b/api/http/mypy.ini @@ -2,6 +2,7 @@ python_version = 3.9 strict = True plugins = pydantic.mypy +exclude = dist [mypy-indigo.*] ignore_missing_imports = True diff --git a/api/http/requirements_dev.txt b/api/http/requirements_dev.txt index c5101f66cf..4e5bc4d930 100644 --- a/api/http/requirements_dev.txt +++ b/api/http/requirements_dev.txt @@ -19,3 +19,4 @@ types-requests==2.26.3 urllib3==1.26.7 isort==5.9.3 pylint==2.11.1 +mypy==0.910