From 961030a8871ae52835e7e05dc63b571949ab7891 Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Thu, 14 Mar 2024 22:14:41 -0400 Subject: [PATCH 1/8] Added fastapi app files --- fastapi/README.md | 26 +++++ fastapi/app/__init__.py | 0 fastapi/app/main.py | 231 ++++++++++++++++++++++++++++++++++++++++ fastapi/environment.yml | 8 ++ 4 files changed, 265 insertions(+) create mode 100644 fastapi/README.md create mode 100644 fastapi/app/__init__.py create mode 100644 fastapi/app/main.py create mode 100644 fastapi/environment.yml diff --git a/fastapi/README.md b/fastapi/README.md new file mode 100644 index 0000000..3792383 --- /dev/null +++ b/fastapi/README.md @@ -0,0 +1,26 @@ +# SpiceQL FastAPI App + +## Create local instance + +### 1. Create conda environment +Create the conda environment to run your local instance in: +``` +conda create -n spiceql-api -f environment.yaml +``` + +### 2. Set environment variables +Similarly to your SpiceQL conda environment, set `SPICEROOT` or `ISISDATA` to your ISIS data area. You may also need to set `SSPICE_DEBUG` to any value, like `True`. + +To set an environment variable within the scope of your conda environment: +``` +conda activate spiceql-api +conda env config vars set SPICEROOT=/path/to/isis_data +``` + +### 3. Run the app +Inside the `app/` dir, run the following command: +``` +uvicorn app.main:app --reload --port 8080 +``` + +You can access the Swagger UI of all the endpoints at http://127.0.0.1:8080/docs. diff --git a/fastapi/app/__init__.py b/fastapi/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fastapi/app/main.py b/fastapi/app/main.py new file mode 100644 index 0000000..d9b63a9 --- /dev/null +++ b/fastapi/app/main.py @@ -0,0 +1,231 @@ +"""Module providing SpiceQL endpoints""" + +from ast import literal_eval +from typing import Annotated, Any, Optional +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field +import pyspiceql + + +# Models +class MessageItem(BaseModel): + message: str + +class BodyModel(BaseModel): + result: Optional[Any] = None + error: Optional[str] = None + +class ResultModel(BodyModel): + result: Any = Field(serialization_alias='return') + +class ErrorModel(BodyModel): + error: str + +class ResponseModel(BaseModel): + statusCode: int + body: dict | str + +# Create FastAPI instance +app = FastAPI() + +# General endpoints +@app.get("/") +async def root(): + return {"message": "Visit the /docs endpoint to see the Swagger UI."} + +@app.post("/customMessage") +async def message( + message_item: MessageItem + ): + return {"message": message_item.message} + + +# SpiceQL endpoints +@app.get("/getTargetStates") +async def getTargetStates( + ets: Annotated[list[float], Query()] | str, + target: str, + observer: str, + frame: str, + abcorr: str, + mission: str, + ckQuality: str = "", + spkQuality: str = "", + searchKernels: bool = False): + try: + if isinstance(ets, str): + ets = literal_eval(ets) + result = pyspiceql.getTargetStates(ets, target, observer, frame, abcorr, mission, ckQuality, spkQuality, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/getTargetOrientations") +async def getTargetOrientations( + ets: Annotated[list[float], Query()] | str, + toFrame: int, + refFrame: int, + mission: str, + ckQuality: str = "", + searchKernels: bool = False): + try: + if isinstance(ets, str): + ets = literal_eval(ets) + result = pyspiceql.getTargetOrientations(ets, toFrame, refFrame, mission, ckQuality, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/strSclkToEt") +async def strSclkToEt( + frameCode: int, + sclk: str, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.strSclkToEt(frameCode, sclk, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/doubleSclkToEt") +async def doubleSclkToEt( + frameCode: int, + sclk: float, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.doubleSclkToEt(frameCode, sclk, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/utcToEt") +async def utcToEt( + utc: str, + searchKernels: bool = False): + try: + result = pyspiceql.utcToEt(utc, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/translateNameToCode") +async def translateNameToCode( + frame: str, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.translateNameToCode(frame, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/translateCodeToName") +async def translateCodeToName( + frame: int, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.translateCodeToName(frame, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/getFrameInfo") +async def getFrameInfo( + frame: int, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.getFrameInfo(frame, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/getTargetFrameInfo") +async def getTargetFrameInfo( + targetId: int, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.getTargetFrameInfo(targetId, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/findMissionKeywords") +async def findMissionKeywords( + key: str, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.findMissionKeywords(key, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/findTargetKeywords") +async def findTargetKeywords( + key: str, + mission: str, + searchKernels: bool = False): + try: + result = pyspiceql.findTargetKeywords(key, mission, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/frameTrace") +async def frameTrace( + et: float, + initialFrame: int, + mission: str, + ckQuality: str = "", + searchKernels: bool = False): + try: + result = pyspiceql.frameTrace(et, initialFrame, mission, ckQuality, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + +@app.get("/extractExactCkTimes") +async def extractExactCkTimes( + observStart: float, + observEnd: float, + targetFrame: int, + mission: str, + ckQuality: str = "", + searchKernels: bool = False): + try: + result = pyspiceql.extractExactCkTimes(observStart, observEnd, targetFrame, mission, ckQuality, searchKernels) + body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + return ResponseModel(statusCode=200, body=body) + except Exception as e: + body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + return ResponseModel(statusCode=500, body=body) + \ No newline at end of file diff --git a/fastapi/environment.yml b/fastapi/environment.yml new file mode 100644 index 0000000..f8564bb --- /dev/null +++ b/fastapi/environment.yml @@ -0,0 +1,8 @@ +channels: + - conda-forge + +dependencies: + - fastapi + - pydantic==2.6.3 + - spiceql + - uvicorn From 32d6f39bb801ae5789a2d5f2dc4a4082b8cfe15e Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Mon, 18 Mar 2024 13:25:11 -0400 Subject: [PATCH 2/8] Refactor for less complicated model structure --- fastapi/app/main.py | 64 +++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/fastapi/app/main.py b/fastapi/app/main.py index d9b63a9..0c7e3a0 100644 --- a/fastapi/app/main.py +++ b/fastapi/app/main.py @@ -1,7 +1,7 @@ """Module providing SpiceQL endpoints""" from ast import literal_eval -from typing import Annotated, Any, Optional +from typing import Annotated, Any from fastapi import FastAPI, Query from pydantic import BaseModel, Field import pyspiceql @@ -11,19 +11,15 @@ class MessageItem(BaseModel): message: str -class BodyModel(BaseModel): - result: Optional[Any] = None - error: Optional[str] = None - -class ResultModel(BodyModel): +class ResultModel(BaseModel): result: Any = Field(serialization_alias='return') -class ErrorModel(BodyModel): +class ErrorModel(BaseModel): error: str class ResponseModel(BaseModel): statusCode: int - body: dict | str + body: ResultModel | ErrorModel # Create FastAPI instance app = FastAPI() @@ -56,10 +52,10 @@ async def getTargetStates( if isinstance(ets, str): ets = literal_eval(ets) result = pyspiceql.getTargetStates(ets, target, observer, frame, abcorr, mission, ckQuality, spkQuality, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/getTargetOrientations") @@ -74,10 +70,10 @@ async def getTargetOrientations( if isinstance(ets, str): ets = literal_eval(ets) result = pyspiceql.getTargetOrientations(ets, toFrame, refFrame, mission, ckQuality, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/strSclkToEt") @@ -88,10 +84,10 @@ async def strSclkToEt( searchKernels: bool = False): try: result = pyspiceql.strSclkToEt(frameCode, sclk, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/doubleSclkToEt") @@ -102,10 +98,10 @@ async def doubleSclkToEt( searchKernels: bool = False): try: result = pyspiceql.doubleSclkToEt(frameCode, sclk, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/utcToEt") @@ -114,10 +110,10 @@ async def utcToEt( searchKernels: bool = False): try: result = pyspiceql.utcToEt(utc, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/translateNameToCode") @@ -127,10 +123,10 @@ async def translateNameToCode( searchKernels: bool = False): try: result = pyspiceql.translateNameToCode(frame, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/translateCodeToName") @@ -140,10 +136,10 @@ async def translateCodeToName( searchKernels: bool = False): try: result = pyspiceql.translateCodeToName(frame, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/getFrameInfo") @@ -153,10 +149,10 @@ async def getFrameInfo( searchKernels: bool = False): try: result = pyspiceql.getFrameInfo(frame, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/getTargetFrameInfo") @@ -166,10 +162,10 @@ async def getTargetFrameInfo( searchKernels: bool = False): try: result = pyspiceql.getTargetFrameInfo(targetId, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/findMissionKeywords") @@ -179,10 +175,10 @@ async def findMissionKeywords( searchKernels: bool = False): try: result = pyspiceql.findMissionKeywords(key, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/findTargetKeywords") @@ -192,10 +188,10 @@ async def findTargetKeywords( searchKernels: bool = False): try: result = pyspiceql.findTargetKeywords(key, mission, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/frameTrace") @@ -207,10 +203,10 @@ async def frameTrace( searchKernels: bool = False): try: result = pyspiceql.frameTrace(et, initialFrame, mission, ckQuality, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) @app.get("/extractExactCkTimes") @@ -223,9 +219,9 @@ async def extractExactCkTimes( searchKernels: bool = False): try: result = pyspiceql.extractExactCkTimes(observStart, observEnd, targetFrame, mission, ckQuality, searchKernels) - body = ResultModel(result=result).model_dump(by_alias=True, exclude_none=True) + body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: - body = ErrorModel(error=str(e)).model_dump(exclude_none=True) + body = ErrorModel(error=str(e)) return ResponseModel(statusCode=500, body=body) \ No newline at end of file From a35a59cdf75953f90b90a8020fe95c22413ab3b1 Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Tue, 23 Apr 2024 16:31:15 -0400 Subject: [PATCH 3/8] Added redirect from root endpoint to docs endpoint --- fastapi/app/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastapi/app/main.py b/fastapi/app/main.py index 0c7e3a0..bfe03ac 100644 --- a/fastapi/app/main.py +++ b/fastapi/app/main.py @@ -4,6 +4,7 @@ from typing import Annotated, Any from fastapi import FastAPI, Query from pydantic import BaseModel, Field +from starlette.responses import RedirectResponse import pyspiceql @@ -27,7 +28,7 @@ class ResponseModel(BaseModel): # General endpoints @app.get("/") async def root(): - return {"message": "Visit the /docs endpoint to see the Swagger UI."} + return RedirectResponse(url="/docs") @app.post("/customMessage") async def message( From c1fba175de6868f71d8efaf354a32b14d68a267c Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Thu, 25 Apr 2024 11:18:50 -0400 Subject: [PATCH 4/8] Add api deps to main env.yml --- environment.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/environment.yml b/environment.yml index 2a34196..1d9535c 100644 --- a/environment.yml +++ b/environment.yml @@ -19,6 +19,11 @@ dependencies: - jsonschema - cereal - spdlog + # API dependencies # + - fastapi + - pydantic==2.6.3 + - spiceql + - uvicorn - pip: - breathe - sphinx-material From fc6d22e0fc3f607a45144f4eca4344339f058f2f Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Thu, 25 Apr 2024 14:04:36 -0400 Subject: [PATCH 5/8] Hide root endpt from schema & set searchKernels to always true --- fastapi/app/main.py | 68 +++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/fastapi/app/main.py b/fastapi/app/main.py index bfe03ac..265643a 100644 --- a/fastapi/app/main.py +++ b/fastapi/app/main.py @@ -7,6 +7,7 @@ from starlette.responses import RedirectResponse import pyspiceql +SEARCH_KERNELS_BOOL = True # Models class MessageItem(BaseModel): @@ -26,7 +27,7 @@ class ResponseModel(BaseModel): app = FastAPI() # General endpoints -@app.get("/") +@app.get("/", include_in_schema=False) async def root(): return RedirectResponse(url="/docs") @@ -47,12 +48,11 @@ async def getTargetStates( abcorr: str, mission: str, ckQuality: str = "", - spkQuality: str = "", - searchKernels: bool = False): + spkQuality: str = ""): try: if isinstance(ets, str): ets = literal_eval(ets) - result = pyspiceql.getTargetStates(ets, target, observer, frame, abcorr, mission, ckQuality, spkQuality, searchKernels) + result = pyspiceql.getTargetStates(ets, target, observer, frame, abcorr, mission, ckQuality, spkQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -65,12 +65,11 @@ async def getTargetOrientations( toFrame: int, refFrame: int, mission: str, - ckQuality: str = "", - searchKernels: bool = False): + ckQuality: str = ""): try: if isinstance(ets, str): ets = literal_eval(ets) - result = pyspiceql.getTargetOrientations(ets, toFrame, refFrame, mission, ckQuality, searchKernels) + result = pyspiceql.getTargetOrientations(ets, toFrame, refFrame, mission, ckQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -81,10 +80,9 @@ async def getTargetOrientations( async def strSclkToEt( frameCode: int, sclk: str, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.strSclkToEt(frameCode, sclk, mission, searchKernels) + result = pyspiceql.strSclkToEt(frameCode, sclk, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -95,10 +93,9 @@ async def strSclkToEt( async def doubleSclkToEt( frameCode: int, sclk: float, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.doubleSclkToEt(frameCode, sclk, mission, searchKernels) + result = pyspiceql.doubleSclkToEt(frameCode, sclk, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -107,10 +104,9 @@ async def doubleSclkToEt( @app.get("/utcToEt") async def utcToEt( - utc: str, - searchKernels: bool = False): + utc: str): try: - result = pyspiceql.utcToEt(utc, searchKernels) + result = pyspiceql.utcToEt(utc, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -120,10 +116,9 @@ async def utcToEt( @app.get("/translateNameToCode") async def translateNameToCode( frame: str, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.translateNameToCode(frame, mission, searchKernels) + result = pyspiceql.translateNameToCode(frame, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -133,10 +128,9 @@ async def translateNameToCode( @app.get("/translateCodeToName") async def translateCodeToName( frame: int, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.translateCodeToName(frame, mission, searchKernels) + result = pyspiceql.translateCodeToName(frame, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -146,10 +140,9 @@ async def translateCodeToName( @app.get("/getFrameInfo") async def getFrameInfo( frame: int, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.getFrameInfo(frame, mission, searchKernels) + result = pyspiceql.getFrameInfo(frame, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -159,10 +152,9 @@ async def getFrameInfo( @app.get("/getTargetFrameInfo") async def getTargetFrameInfo( targetId: int, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.getTargetFrameInfo(targetId, mission, searchKernels) + result = pyspiceql.getTargetFrameInfo(targetId, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -172,10 +164,9 @@ async def getTargetFrameInfo( @app.get("/findMissionKeywords") async def findMissionKeywords( key: str, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.findMissionKeywords(key, mission, searchKernels) + result = pyspiceql.findMissionKeywords(key, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -185,10 +176,9 @@ async def findMissionKeywords( @app.get("/findTargetKeywords") async def findTargetKeywords( key: str, - mission: str, - searchKernels: bool = False): + mission: str): try: - result = pyspiceql.findTargetKeywords(key, mission, searchKernels) + result = pyspiceql.findTargetKeywords(key, mission, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -200,10 +190,9 @@ async def frameTrace( et: float, initialFrame: int, mission: str, - ckQuality: str = "", - searchKernels: bool = False): + ckQuality: str = ""): try: - result = pyspiceql.frameTrace(et, initialFrame, mission, ckQuality, searchKernels) + result = pyspiceql.frameTrace(et, initialFrame, mission, ckQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: @@ -216,10 +205,9 @@ async def extractExactCkTimes( observEnd: float, targetFrame: int, mission: str, - ckQuality: str = "", - searchKernels: bool = False): + ckQuality: str = ""): try: - result = pyspiceql.extractExactCkTimes(observStart, observEnd, targetFrame, mission, ckQuality, searchKernels) + result = pyspiceql.extractExactCkTimes(observStart, observEnd, targetFrame, mission, ckQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) except Exception as e: From 1043c49da4ddbdd8de65f1338b27520fa26f6d27 Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Thu, 25 Apr 2024 17:57:47 -0400 Subject: [PATCH 6/8] Remove spiceql dep --- environment.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index 1d9535c..132ad7d 100644 --- a/environment.yml +++ b/environment.yml @@ -19,10 +19,9 @@ dependencies: - jsonschema - cereal - spdlog - # API dependencies # + # API dependencies - fastapi - pydantic==2.6.3 - - spiceql - uvicorn - pip: - breathe From 6506e05f571c3016b584de57ae85683b88367a3e Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Thu, 25 Apr 2024 17:59:46 -0400 Subject: [PATCH 7/8] Update readme --- fastapi/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastapi/README.md b/fastapi/README.md index 3792383..c74a1a0 100644 --- a/fastapi/README.md +++ b/fastapi/README.md @@ -5,7 +5,7 @@ ### 1. Create conda environment Create the conda environment to run your local instance in: ``` -conda create -n spiceql-api -f environment.yaml +conda env update -n spiceql-api -f environment.yaml ``` ### 2. Set environment variables @@ -18,7 +18,7 @@ conda env config vars set SPICEROOT=/path/to/isis_data ``` ### 3. Run the app -Inside the `app/` dir, run the following command: +Within the `fastapi/` dir but outside the `app/` dir, run the following command: ``` uvicorn app.main:app --reload --port 8080 ``` From 1037429b9b0654d27c7f6ffb5010b39a328da0d6 Mon Sep 17 00:00:00 2001 From: Christine Kim Date: Fri, 26 Apr 2024 12:26:36 -0400 Subject: [PATCH 8/8] Update getTargetOrientations and getTargetStates endpoints --- environment.yml | 1 + fastapi/app/main.py | 35 +++++++++++++++++++++++++++++------ fastapi/environment.yml | 8 -------- 3 files changed, 30 insertions(+), 14 deletions(-) delete mode 100644 fastapi/environment.yml diff --git a/environment.yml b/environment.yml index 132ad7d..e38f903 100644 --- a/environment.yml +++ b/environment.yml @@ -23,6 +23,7 @@ dependencies: - fastapi - pydantic==2.6.3 - uvicorn + - numpy - pip: - breathe - sphinx-material diff --git a/fastapi/app/main.py b/fastapi/app/main.py index 265643a..95fa31f 100644 --- a/fastapi/app/main.py +++ b/fastapi/app/main.py @@ -5,6 +5,7 @@ from fastapi import FastAPI, Query from pydantic import BaseModel, Field from starlette.responses import RedirectResponse +import numpy as np import pyspiceql SEARCH_KERNELS_BOOL = True @@ -41,17 +42,28 @@ async def message( # SpiceQL endpoints @app.get("/getTargetStates") async def getTargetStates( - ets: Annotated[list[float], Query()] | str, target: str, observer: str, frame: str, abcorr: str, mission: str, + ets: Annotated[list[float], Query()] | str | None = None, + startEts: float | None = None, + exposureDuration: float | None = None, + numOfExposures: int | None = None, ckQuality: str = "", spkQuality: str = ""): try: - if isinstance(ets, str): - ets = literal_eval(ets) + if ets is not None: + if isinstance(ets, str): + ets = literal_eval(ets) + else: + if all(v is not None for v in [startEts, exposureDuration, numOfExposures]): + stopEts = (exposureDuration * numOfExposures) + startEts + etsNpArray = np.arange(startEts, stopEts, exposureDuration) + ets = list(etsNpArray) + else: + raise Exception("Verify that a startEts, exposureDuration, and numOfExposures are being passed correctly.") result = pyspiceql.getTargetStates(ets, target, observer, frame, abcorr, mission, ckQuality, spkQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) @@ -61,14 +73,25 @@ async def getTargetStates( @app.get("/getTargetOrientations") async def getTargetOrientations( - ets: Annotated[list[float], Query()] | str, toFrame: int, refFrame: int, mission: str, + ets: Annotated[list[float], Query()] | str | None = None, + startEts: float | None = None, + exposureDuration: float | None = None, + numOfExposures: int | None = None, ckQuality: str = ""): try: - if isinstance(ets, str): - ets = literal_eval(ets) + if ets is not None: + if isinstance(ets, str): + ets = literal_eval(ets) + else: + if all(v is not None for v in [startEts, exposureDuration, numOfExposures]): + stopEts = (exposureDuration * numOfExposures) + startEts + etsNpArray = np.arange(startEts, stopEts, exposureDuration) + ets = list(etsNpArray) + else: + raise Exception("Verify that a startEts, exposureDuration, and numOfExposures are being passed correctly.") result = pyspiceql.getTargetOrientations(ets, toFrame, refFrame, mission, ckQuality, SEARCH_KERNELS_BOOL) body = ResultModel(result=result) return ResponseModel(statusCode=200, body=body) diff --git a/fastapi/environment.yml b/fastapi/environment.yml deleted file mode 100644 index f8564bb..0000000 --- a/fastapi/environment.yml +++ /dev/null @@ -1,8 +0,0 @@ -channels: - - conda-forge - -dependencies: - - fastapi - - pydantic==2.6.3 - - spiceql - - uvicorn