Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RestX & Swagger #9

Merged
merged 13 commits into from
Dec 6, 2024
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ The Java Gateway enables the Flask application to communicate with a JVM using [

The Fink cutout API is a Flask application to access cutouts from the Fink datalake. We only store cutout metadata in HBase, and this API retrieves the data from the raw parquet files stored on HDFS.

_From 2019 to 2024, the development of this API was done in [fink-science-portal](https://github.com/astrolabsoftware/fink-science-portal). Check this repository for older issues and PR._

## Documentation

The user documentation can be found at this [link](https://fink-broker.readthedocs.io/en/latest/services/search/getting_started/#quick-start-api). Documentation for developpers and maintainers can be found on [GitLab](https://gitlab.in2p3.fr/fink/rubin-performance-check/-/blob/main/portal/README.md?ref_type=heads) (auth required).
There are several forms of documentation, depending on what you are looking for:

- Tutorials/How-to guides: [Fink user manual](https://fink-broker.readthedocs.io/en/latest/services/search/getting_started/#quick-start-api)
- API Reference guide: [https://api.fink-portal.org](https://api.fink-portal.org)
- Notes for developpers and maintainers (auth required): [GitLab](https://gitlab.in2p3.fr/fink/rubin-performance-check/-/blob/main/portal/README.md?ref_type=heads)

## Requirements and installation

Expand All @@ -24,15 +30,15 @@ The input parameters can be found in [config.yml](config.yml). Make sure that th

### Debug

After starting [fink-cutout-api](https://github.com/astrolabsoftware/fink-cutout-api), you can simply test the API using:
After starting the Fink Java Gateway and [fink-cutout-api](https://github.com/astrolabsoftware/fink-cutout-api) services, you can simply launch the API in debug mode using:

```bash
python app.py
```

### Production

The application is simply managed by `gunicorn` and `systemd` (see [install](install/README.md)), and you can simply manage it using:
The application is simply managed by `gunicorn` and `systemd` (see [install](install/README.md)), and you can manage it using:

```bash
# start the application
Expand Down
37 changes: 32 additions & 5 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,51 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from flask import Flask
from flask import Flask, Blueprint
from flask_restx import Api

from apps import __version__

from apps.utils.utils import extract_configuration

from apps.routes.objects.api import bp as bp_objects
from apps.routes.cutouts.api import bp as bp_cutouts
from apps.routes.objects.api import ns as ns_objects
from apps.routes.cutouts.api import ns as ns_cutouts

config = extract_configuration("config.yml")

app = Flask("Fink REST API")

# Master blueprint
blueprint = Blueprint("api", __name__, url_prefix="/")
api = Api(
blueprint,
version=__version__,
title="Fink object API",
description="REST API to access data from Fink",
)


# Enable CORS for this blueprint
@blueprint.after_request
def after_request(response):
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
response.headers.add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS")
return response


# Server configuration
app.config["MAX_CONTENT_LENGTH"] = 100 * 1024 * 1024
app.config["JSONIFY_PRETTYPRINT_REGULAR"] = True
app.config["JSON_SORT_KEYS"] = False

app.register_blueprint(bp_objects)
app.register_blueprint(bp_cutouts)
# Register namespace
api.add_namespace(ns_objects)
api.add_namespace(ns_cutouts)

# Register blueprint
app.register_blueprint(blueprint)


if __name__ == "__main__":
app.run(config["HOST"], debug=True, port=int(config["PORT"]))
150 changes: 74 additions & 76 deletions apps/routes/cutouts/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,93 +12,91 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from flask import Blueprint, Response, jsonify, request
from flask import Response, request
from flask_restx import Namespace, Resource, fields

from apps.utils.utils import check_args

from apps.routes.cutouts.utils import format_and_send_cutout

bp = Blueprint("cutouts", __name__)


# Enable CORS for this blueprint
@bp.after_request
def after_request(response):
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
response.headers.add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS")
return response
ns = Namespace("api/v1/cutouts", "Get cutout data based on ZTF ID")


ARGS = [
{
"name": "objectId",
"required": True,
"description": "ZTF Object ID",
},
{
"name": "kind",
"required": True,
"description": "Science, Template, or Difference. For output-format=array, you can also specify `kind: All` to get the 3 cutouts.",
},
{
"name": "output-format",
"required": False,
"description": "PNG[default], FITS, array",
},
{
"name": "candid",
"required": False,
"description": "Candidate ID of the alert belonging to the object with `objectId`. If not filled, the cutouts of the latest alert is returned",
},
{
"name": "stretch",
"required": False,
"description": "Stretch function to be applied. Available: sigmoid[default], linear, sqrt, power, log.",
},
{
"name": "colormap",
"required": False,
"description": "Valid matplotlib colormap name (see matplotlib.cm). Default is grayscale.",
},
{
"name": "pmin",
"required": False,
"description": "The percentile value used to determine the pixel value of minimum cut level. Default is 0.5. No effect for sigmoid.",
},
{
"name": "pmax",
"required": False,
"description": "The percentile value used to determine the pixel value of maximum cut level. Default is 99.5. No effect for sigmoid.",
},
ARGS = ns.model(
"cutouts",
{
"name": "convolution_kernel",
"required": False,
"description": "Convolve the image with a kernel (gauss or box). Default is None (not specified).",
"objectId": fields.String(
description="ZTF Object ID",
example="ZTF24abuunge",
required=True,
),
"kind": fields.String(
description="Science, Template, or Difference. For output-format=array, you can also specify `kind: All` to get the 3 cutouts.",
example="Science",
required=True,
),
"output-format": fields.String(
description="PNG[default], FITS, array", example="array", required=False
),
"candid": fields.Integer(
description="Candidate ID of the alert belonging to the object with `objectId`. If not filled, the cutouts of the latest alert is returned",
example=2890466950515015016,
required=False,
),
"stretch": fields.String(
description="Stretch function to be applied. Available: sigmoid[default], linear, sqrt, power, log.",
example="sigmoid",
required=False,
),
"colormap": fields.String(
description="Valid matplotlib colormap name (see matplotlib.cm). Default is grayscale.",
example="Blues",
required=False,
),
"pmin": fields.Float(
description="The percentile value used to determine the pixel value of minimum cut level. Default is 0.5. No effect for sigmoid.",
example=0.5,
required=False,
),
"pmax": fields.Float(
description="The percentile value used to determine the pixel value of maximum cut level. Default is 99.5. No effect for sigmoid.",
example=99.5,
required=False,
),
"convolution_kernel": fields.String(
description="Convolve the image with a kernel (gauss or box). If not specified, no kernel is applied.",
example="gauss",
required=False,
),
},
]
)


@bp.route("/api/v1/cutouts", methods=["GET"])
def return_cutouts_arguments():
"""Obtain information about cutouts"""
if len(request.args) > 0:
# POST from query URL
return return_cutouts(payload=request.args)
else:
return jsonify({"args": ARGS})
@ns.route("/")
@ns.doc(params={k: ARGS[k].description for k in ARGS})
class Cutouts(Resource):
def get(self):
"""Retrieve cutout data from the Fink/ZTF datalake"""
payload = request.args
if len(payload) > 0:
# POST from query URL
return self.post()
else:
return Response(ARGS.description, 200)

@ns.expect(ARGS, location="json", as_dict=True)
def post(self):
"""Retrieve cutout data from the Fink/ZTF datalake"""
# get payload from the query URL
payload = request.args

@bp.route("/api/v1/cutouts", methods=["POST"])
def return_cutouts(payload=None):
"""Retrieve object data"""
# get payload from the JSON
if payload is None:
payload = request.json
if payload is None or len(payload) == 0:
# if no payload, try the JSON blob
payload = request.json

rep = check_args(ARGS, payload)
if rep["status"] != "ok":
return Response(str(rep), 400)
rep = check_args(ARGS, payload)
if rep["status"] != "ok":
return Response(str(rep), 400)

assert payload["kind"] in ["Science", "Template", "Difference", "All"]
assert payload["kind"] in ["Science", "Template", "Difference", "All"]

return format_and_send_cutout(payload)
return format_and_send_cutout(payload)
1 change: 1 addition & 0 deletions apps/routes/cutouts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def format_and_send_cutout(payload: dict):
group_alerts=False,
truncated=True,
extract_color=False,
escape_slash=True,
)

json_payload = {}
Expand Down
Loading
Loading