-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added fastapi app files * Refactor for less complicated model structure * Added redirect from root endpoint to docs endpoint * Add api deps to main env.yml * Hide root endpt from schema & set searchKernels to always true * Remove spiceql dep * Update readme * Update getTargetOrientations and getTargetStates endpoints
- Loading branch information
1 parent
2dfba0c
commit 10bd3f3
Showing
4 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 env update -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 | ||
Within the `fastapi/` dir but outside 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. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
"""Module providing SpiceQL endpoints""" | ||
|
||
from ast import literal_eval | ||
from typing import Annotated, Any | ||
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 | ||
|
||
# Models | ||
class MessageItem(BaseModel): | ||
message: str | ||
|
||
class ResultModel(BaseModel): | ||
result: Any = Field(serialization_alias='return') | ||
|
||
class ErrorModel(BaseModel): | ||
error: str | ||
|
||
class ResponseModel(BaseModel): | ||
statusCode: int | ||
body: ResultModel | ErrorModel | ||
|
||
# Create FastAPI instance | ||
app = FastAPI() | ||
|
||
# General endpoints | ||
@app.get("/", include_in_schema=False) | ||
async def root(): | ||
return RedirectResponse(url="/docs") | ||
|
||
@app.post("/customMessage") | ||
async def message( | ||
message_item: MessageItem | ||
): | ||
return {"message": message_item.message} | ||
|
||
|
||
# SpiceQL endpoints | ||
@app.get("/getTargetStates") | ||
async def getTargetStates( | ||
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 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) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/getTargetOrientations") | ||
async def getTargetOrientations( | ||
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 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) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/strSclkToEt") | ||
async def strSclkToEt( | ||
frameCode: int, | ||
sclk: str, | ||
mission: str): | ||
try: | ||
result = pyspiceql.strSclkToEt(frameCode, sclk, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/doubleSclkToEt") | ||
async def doubleSclkToEt( | ||
frameCode: int, | ||
sclk: float, | ||
mission: str): | ||
try: | ||
result = pyspiceql.doubleSclkToEt(frameCode, sclk, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/utcToEt") | ||
async def utcToEt( | ||
utc: str): | ||
try: | ||
result = pyspiceql.utcToEt(utc, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/translateNameToCode") | ||
async def translateNameToCode( | ||
frame: str, | ||
mission: str): | ||
try: | ||
result = pyspiceql.translateNameToCode(frame, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/translateCodeToName") | ||
async def translateCodeToName( | ||
frame: int, | ||
mission: str): | ||
try: | ||
result = pyspiceql.translateCodeToName(frame, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/getFrameInfo") | ||
async def getFrameInfo( | ||
frame: int, | ||
mission: str): | ||
try: | ||
result = pyspiceql.getFrameInfo(frame, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/getTargetFrameInfo") | ||
async def getTargetFrameInfo( | ||
targetId: int, | ||
mission: str): | ||
try: | ||
result = pyspiceql.getTargetFrameInfo(targetId, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/findMissionKeywords") | ||
async def findMissionKeywords( | ||
key: str, | ||
mission: str): | ||
try: | ||
result = pyspiceql.findMissionKeywords(key, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/findTargetKeywords") | ||
async def findTargetKeywords( | ||
key: str, | ||
mission: str): | ||
try: | ||
result = pyspiceql.findTargetKeywords(key, mission, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/frameTrace") | ||
async def frameTrace( | ||
et: float, | ||
initialFrame: int, | ||
mission: str, | ||
ckQuality: str = ""): | ||
try: | ||
result = pyspiceql.frameTrace(et, initialFrame, mission, ckQuality, SEARCH_KERNELS_BOOL) | ||
body = ResultModel(result=result) | ||
return ResponseModel(statusCode=200, body=body) | ||
except Exception as e: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|
||
@app.get("/extractExactCkTimes") | ||
async def extractExactCkTimes( | ||
observStart: float, | ||
observEnd: float, | ||
targetFrame: int, | ||
mission: str, | ||
ckQuality: str = ""): | ||
try: | ||
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: | ||
body = ErrorModel(error=str(e)) | ||
return ResponseModel(statusCode=500, body=body) | ||
|