Skip to content

Commit

Permalink
Merge pull request #344 from ral-facilities/add-openapi-docs-for-sear…
Browse files Browse the repository at this point in the history
…ch-api-#281

Add OpenAPI docs for Search API
  • Loading branch information
VKTB authored Feb 28, 2022
2 parents 8ef98aa + d4fc795 commit aa98c51
Show file tree
Hide file tree
Showing 6 changed files with 1,096 additions and 54 deletions.
129 changes: 101 additions & 28 deletions datagateway_api/src/api_start_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
instrument_investigation_endpoint,
)
from datagateway_api.src.swagger.apispec_flask_restful import RestfulPlugin
from datagateway_api.src.swagger.initialise_spec import initialise_spec
from datagateway_api.src.swagger.initialise_spec import (
initialise_datagateway_api_spec,
initialise_search_api_spec,
)

log = logging.getLogger()

Expand All @@ -64,19 +67,54 @@ def handle_error(self, e):
return error_msg, e.status_code


def create_app_infrastructure(flask_app):
def configure_datagateway_api_swaggerui_blueprint(flask_app):
swaggerui_blueprint = get_swaggerui_blueprint(
base_url=Config.config.datagateway_api.extension,
api_url="/datagateway-api/openapi.json",
config={"app_name": "DataGateway API OpenAPI Spec"},
blueprint_name="DataGateway API Swagger UI",
)
flask_app.register_blueprint(
swaggerui_blueprint,
url_prefix=Config.config.datagateway_api.extension,
name_prefix="DataGateway API",
)


def configure_search_api_swaggerui_blueprint(flask_app):
swaggerui_blueprint = get_swaggerui_blueprint(
"", "/openapi.json", config={"app_name": "DataGateway API OpenAPI Spec"},
base_url=Config.config.search_api.extension,
api_url="/search-api/openapi.json",
config={"app_name": "Search API OpenAPI Spec"},
blueprint_name="Search API Swagger UI",
)
flask_app.register_blueprint(
swaggerui_blueprint,
url_prefix=Config.config.search_api.extension,
name_prefix="Search API",
)
flask_app.register_blueprint(swaggerui_blueprint, url_prefix="/")
spec = APISpec(


def create_datagateway_api_spec():
return APISpec(
title="DataGateway API",
version="1.0",
openapi_version="3.0.3",
plugins=[RestfulPlugin()],
security=[{"session_id": []}],
)


def create_search_api_spec():
return APISpec(
title="Search API",
version="1.0",
openapi_version="3.0.3",
plugins=[RestfulPlugin()],
)


def create_app_infrastructure(flask_app):
CORS(flask_app)
flask_app.url_map.strict_slashes = False
api = CustomErrorHandledApi(flask_app)
Expand All @@ -95,14 +133,27 @@ def create_app_infrastructure(flask_app):
flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(flask_app)

initialise_spec(spec)
specs = []
if Config.config.datagateway_api is not None:
configure_datagateway_api_swaggerui_blueprint(flask_app)
datagateway_api_spec = create_datagateway_api_spec()
initialise_datagateway_api_spec(datagateway_api_spec)
specs.append(datagateway_api_spec)
if Config.config.search_api is not None:
configure_search_api_swaggerui_blueprint(flask_app)
search_api_spec = create_search_api_spec()
initialise_search_api_spec(search_api_spec)
specs.append(search_api_spec)

return (api, spec)
return api, specs


def create_api_endpoints(flask_app, api, spec):
def create_api_endpoints(flask_app, api, specs):
# DataGateway API endpoints
if Config.config.datagateway_api is not None:
datagateway_api_spec = next(
(spec for spec in specs if spec.title == "DataGateway API"), None,
)
try:
backend_type = flask_app.config["TEST_BACKEND"]
Config.config.datagateway_api.set_backend_type(backend_type)
Expand All @@ -129,7 +180,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/{entity_name.lower()}",
endpoint=f"datagateway_get_{entity_name}",
)
spec.path(resource=get_endpoint_resource, api=api)
datagateway_api_spec.path(resource=get_endpoint_resource, api=api)

get_id_endpoint_resource = get_id_endpoint(
entity_name,
Expand All @@ -142,7 +193,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/{entity_name.lower()}/<int:id_>",
endpoint=f"datagateway_get_id_{entity_name}",
)
spec.path(resource=get_id_endpoint_resource, api=api)
datagateway_api_spec.path(resource=get_id_endpoint_resource, api=api)

get_count_endpoint_resource = get_count_endpoint(
entity_name,
Expand All @@ -155,7 +206,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/{entity_name.lower()}/count",
endpoint=f"datagateway_count_{entity_name}",
)
spec.path(resource=get_count_endpoint_resource, api=api)
datagateway_api_spec.path(resource=get_count_endpoint_resource, api=api)

get_find_one_endpoint_resource = get_find_one_endpoint(
entity_name,
Expand All @@ -168,7 +219,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/{entity_name.lower()}/findone",
endpoint=f"datagateway_findone_{entity_name}",
)
spec.path(resource=get_find_one_endpoint_resource, api=api)
datagateway_api_spec.path(resource=get_find_one_endpoint_resource, api=api)

# Session endpoint
session_endpoint_resource = session_endpoints(
Expand All @@ -179,7 +230,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/sessions",
endpoint="datagateway_sessions",
)
spec.path(resource=session_endpoint_resource, api=api)
datagateway_api_spec.path(resource=session_endpoint_resource, api=api)

# Table specific endpoints
instrument_facility_cycle_resource = instrument_facility_cycles_endpoint(
Expand All @@ -190,7 +241,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/instruments/<int:id_>/facilitycycles",
endpoint="datagateway_isis_instrument_facility_cycle",
)
spec.path(resource=instrument_facility_cycle_resource, api=api)
datagateway_api_spec.path(resource=instrument_facility_cycle_resource, api=api)

count_instrument_facility_cycle_res = count_instrument_facility_cycles_endpoint(
backend, client_pool=icat_client_pool,
Expand All @@ -200,7 +251,7 @@ def create_api_endpoints(flask_app, api, spec):
f"{datagateway_api_extension}/instruments/<int:id_>/facilitycycles/count",
endpoint="datagateway_isis_count_instrument_facility_cycle",
)
spec.path(resource=count_instrument_facility_cycle_res, api=api)
datagateway_api_spec.path(resource=count_instrument_facility_cycle_res, api=api)

instrument_investigation_resource = instrument_investigation_endpoint(
backend, client_pool=icat_client_pool,
Expand All @@ -211,7 +262,7 @@ def create_api_endpoints(flask_app, api, spec):
f"/facilitycycles/<int:cycle_id>/investigations",
endpoint="datagateway_isis_instrument_investigation",
)
spec.path(resource=instrument_investigation_resource, api=api)
datagateway_api_spec.path(resource=instrument_investigation_resource, api=api)

count_instrument_investigation_res = count_instrument_investigation_endpoint(
backend, client_pool=icat_client_pool,
Expand All @@ -222,15 +273,18 @@ def create_api_endpoints(flask_app, api, spec):
f"/facilitycycles/<int:cycle_id>/investigations/count",
endpoint="datagateway_isis_count_instrument_investigation",
)
spec.path(resource=count_instrument_investigation_res, api=api)
datagateway_api_spec.path(resource=count_instrument_investigation_res, api=api)

# Ping endpoint
ping_resource = ping_endpoint(backend, client_pool=icat_client_pool)
api.add_resource(ping_resource, f"{datagateway_api_extension}/ping")
spec.path(resource=ping_resource, api=api)
datagateway_api_spec.path(resource=ping_resource, api=api)

# Search API endpoints
if Config.config.search_api is not None:
search_api_spec = next(
(spec for spec in specs if spec.title == "Search API"), None,
)
search_api_extension = Config.config.search_api.extension
search_api_entity_endpoints = {
"datasets": "Dataset",
Expand All @@ -245,31 +299,31 @@ def create_api_endpoints(flask_app, api, spec):
f"{search_api_extension}/{endpoint_name}",
endpoint=f"search_api_get_{endpoint_name}",
)
spec.path(resource=get_search_endpoint_resource, api=api)
search_api_spec.path(resource=get_search_endpoint_resource, api=api)

get_single_endpoint_resource = get_single_endpoint(entity_name)
api.add_resource(
get_single_endpoint_resource,
f"{search_api_extension}/{endpoint_name}/<string:pid>",
endpoint=f"search_api_get_single_{endpoint_name}",
)
spec.path(resource=get_single_endpoint_resource, api=api)
search_api_spec.path(resource=get_single_endpoint_resource, api=api)

get_number_count_endpoint_resource = get_number_count_endpoint(entity_name)
api.add_resource(
get_number_count_endpoint_resource,
f"{search_api_extension}/{endpoint_name}/count",
endpoint=f"search_api_count_{endpoint_name}",
)
spec.path(resource=get_number_count_endpoint_resource, api=api)
search_api_spec.path(resource=get_number_count_endpoint_resource, api=api)

get_files_endpoint_resource = get_files_endpoint("File")
api.add_resource(
get_files_endpoint_resource,
f"{search_api_extension}/datasets/<string:pid>/files",
endpoint="search_api_get_dataset_files",
)
spec.path(resource=get_files_endpoint_resource, api=api)
search_api_spec.path(resource=get_files_endpoint_resource, api=api)

get_number_count_files_endpoint_resource = get_number_count_files_endpoint(
"File",
Expand All @@ -279,10 +333,10 @@ def create_api_endpoints(flask_app, api, spec):
f"{search_api_extension}/datasets/<string:pid>/files/count",
endpoint="search_api_count_dataset_files",
)
spec.path(resource=get_number_count_files_endpoint_resource, api=api)
search_api_spec.path(resource=get_number_count_files_endpoint_resource, api=api)


def openapi_config(spec):
def openapi_config(spec, openapi_spec_path):
# Reorder paths (e.g. get, patch, post) so openapi.yaml only changes when there's a
# change to the Swagger docs, rather than changing on each startup
if Config.config.generate_swagger:
Expand All @@ -291,14 +345,33 @@ def openapi_config(spec):
for endpoint_name in sorted(entity_data.keys()):
entity_data.move_to_end(endpoint_name)

openapi_spec_path = Path(__file__).parent / "swagger/openapi.yaml"
openapi_spec_path = Path(__file__).parent / openapi_spec_path
with open(openapi_spec_path, "w") as f:
f.write(spec.to_yaml())


def create_openapi_endpoint(app, api_spec):
@app.route("/openapi.json")
def specs():
def create_openapi_endpoints(app, api_specs):
for api_spec in api_specs:
if api_spec.title == "DataGateway API":
openapi_config(api_spec, "swagger/datagateway_api/openapi.yaml")
_create_datagateway_openapi_endpoint(app, api_spec)

if api_spec.title == "Search API":
openapi_config(api_spec, "swagger/search_api/openapi.yaml")
_create_search_openapi_endpoint(app, api_spec)


def _create_datagateway_openapi_endpoint(app, api_spec):
@app.route("/datagateway-api/openapi.json")
def datagateway_api_specs():
resp = app.make_response(json.dumps(api_spec.to_dict(), indent=2))
resp.mimetype = "application/json"
return resp


def _create_search_openapi_endpoint(app, api_spec):
@app.route("/search-api/openapi.json")
def search_api_specs():
resp = app.make_response(json.dumps(api_spec.to_dict(), indent=2))
resp.mimetype = "application/json"
return resp
10 changes: 4 additions & 6 deletions datagateway_api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from datagateway_api.src.api_start_utils import (
create_api_endpoints,
create_app_infrastructure,
create_openapi_endpoint,
openapi_config,
create_openapi_endpoints,
)
from datagateway_api.src.common.config import Config
from datagateway_api.src.common.logger_setup import setup_logger
Expand All @@ -16,10 +15,9 @@
log.info("Logging now setup")

app = Flask(__name__)
api, spec = create_app_infrastructure(app)
create_api_endpoints(app, api, spec)
openapi_config(spec)
create_openapi_endpoint(app, spec)
api, specs = create_app_infrastructure(app)
create_api_endpoints(app, api, specs)
create_openapi_endpoints(app, specs)

if __name__ == "__main__":
app.run(
Expand Down
Loading

0 comments on commit aa98c51

Please sign in to comment.