From 6d1dcece0e9a40ef072b0e2b7828636090193c7f Mon Sep 17 00:00:00 2001 From: Tom Kralidis Date: Wed, 30 Sep 2020 07:07:41 -0400 Subject: [PATCH] app mount updates (#548) * refactor WSGI mounting and add docs * fix ref * fix Starlette routing for OACov --- docs/source/running.rst | 44 ++++++++++++++++++++- pygeoapi/api.py | 2 +- pygeoapi/flask_app.py | 42 ++++++++++---------- pygeoapi/starlette_app.py | 80 +++++++++++++++++++-------------------- 4 files changed, 104 insertions(+), 64 deletions(-) diff --git a/docs/source/running.rst b/docs/source/running.rst index 8e11ef3..9bbeb91 100644 --- a/docs/source/running.rst +++ b/docs/source/running.rst @@ -38,6 +38,25 @@ The Flask WSGI server can be run as follows: pygeoapi serve --flask pygeoapi serve # uses Flask by default +To integrate pygeoapi as part of another Flask application, use Flask blueprints: + +.. code-block:: python + + from flask import Flask + from pygeoapi.flask_app import BLUEPRINT as pygeoapi_blueprint + + app = Flask(__name__) + + app.register_blueprint(pygeoapi_blueprint, url_prefix='/oapi') + + + @app.route('/') + def hello_world(): + return 'Hello, World!' + + +As a result, your application will be available at http://localhost:5000/ and pygeoapi will be available +at http://localhost:5000/oapi Starlette ASGI ^^^^^^^^^^^^^^ @@ -51,12 +70,35 @@ Starlette is an ASGI implementation which pygeoapi utilizes to communicate with HTTP request <--> Starlette (pygeoapi/starlette_app.py) <--> pygeoapi API (pygeoapi/api.py) -The Flask WSGI server can be run as follows: +The Starlette ASGI server can be run as follows: .. code-block:: bash pygeoapi serve --starlette +To integrate pygeoapi as part of another Starlette application: + + +.. code-block:: python + + from starlette.applications import Starlette + from starlette.responses import PlainTextResponse + from starlette.routing import Route + from pygeoapi.starlette_app import app as pygeoapi_app + + + async def homepage(request): + return PlainTextResponse('Hello, World!') + + app = Starlette(debug=True, routes=[ + Route('/', homepage), + ]) + + app.mount('/oapi', pygeoapi_app) + + +As a result, your application will be available at http://localhost:5000/ and pygeoapi will be available +at http://localhost:5000/oapi Running in production --------------------- diff --git a/pygeoapi/api.py b/pygeoapi/api.py index fd2e75d..9c0afc3 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -1379,7 +1379,7 @@ class API: headers_ = HEADERS.copy() - format_ = check_format(args, headers_) + format_ = check_format(args, headers) if format_ is None: format_ = 'json' diff --git a/pygeoapi/flask_app.py b/pygeoapi/flask_app.py index 5a2ec9e..3ac300a 100644 --- a/pygeoapi/flask_app.py +++ b/pygeoapi/flask_app.py @@ -39,7 +39,7 @@ from flask import Flask, Blueprint, make_response, request, send_from_directory from pygeoapi.api import API from pygeoapi.util import get_mimetype, yaml_load -routes = Blueprint('pygeoapi', __name__) +BLUEPRINT = Blueprint('pygeoapi', __name__) CONFIG = None @@ -95,7 +95,7 @@ if (OGC_SCHEMAS_LOCATION is not None and mimetype=get_mimetype(basename_)) -@routes.route('/') +@BLUEPRINT.route('/') def landing_page(): """ OGC API landing page endpoint @@ -113,7 +113,7 @@ def landing_page(): return response -@routes.route('/openapi') +@BLUEPRINT.route('/openapi') def openapi(): """ OpenAPI endpoint @@ -134,7 +134,7 @@ def openapi(): return response -@routes.route('/conformance') +@BLUEPRINT.route('/conformance') def conformance(): """ OGC API conformance endpoint @@ -153,8 +153,8 @@ def conformance(): return response -@routes.route('/collections') -@routes.route('/collections/') +@BLUEPRINT.route('/collections') +@BLUEPRINT.route('/collections/') def collections(collection_id=None): """ OGC API collections endpoint @@ -175,7 +175,7 @@ def collections(collection_id=None): return response -@routes.route('/collections//queryables') +@BLUEPRINT.route('/collections//queryables') def collection_queryables(collection_id=None): """ OGC API collections querybles endpoint @@ -196,8 +196,8 @@ def collection_queryables(collection_id=None): return response -@routes.route('/collections//items') -@routes.route('/collections//items/') +@BLUEPRINT.route('/collections//items') +@BLUEPRINT.route('/collections//items/') def collection_items(collection_id, item_id=None): """ OGC API collections items endpoint @@ -223,7 +223,7 @@ def collection_items(collection_id, item_id=None): return response -@routes.route('/collections//coverage') +@BLUEPRINT.route('/collections//coverage') def collection_coverage(collection_id): """ OGC API - Coverages coverage endpoint @@ -244,7 +244,7 @@ def collection_coverage(collection_id): return response -@routes.route('/collections//coverage/domainset') +@BLUEPRINT.route('/collections//coverage/domainset') def collection_coverage_domainset(collection_id): """ OGC API - Coverages coverage domainset endpoint @@ -265,7 +265,7 @@ def collection_coverage_domainset(collection_id): return response -@routes.route('/collections//coverage/rangetype') +@BLUEPRINT.route('/collections//coverage/rangetype') def collection_coverage_rangetype(collection_id): """ OGC API - Coverages coverage rangetype endpoint @@ -286,7 +286,7 @@ def collection_coverage_rangetype(collection_id): return response -@routes.route('/collections//tiles') +@BLUEPRINT.route('/collections//tiles') def get_collection_tiles(collection_id=None): """ OGC open api collections tiles access point @@ -307,7 +307,7 @@ def get_collection_tiles(collection_id=None): return response -@routes.route('/collections//tiles//metadata') +@BLUEPRINT.route('/collections//tiles//metadata') # noqa def get_collection_tiles_metadata(collection_id=None, tileMatrixSetId=None): """ OGC open api collection tiles service metadata @@ -329,7 +329,7 @@ def get_collection_tiles_metadata(collection_id=None, tileMatrixSetId=None): return response -@routes.route('/collections//tiles/\ +@BLUEPRINT.route('/collections//tiles/\ ///') def get_collection_tiles_data(collection_id=None, tileMatrixSetId=None, tileMatrix=None, tileRow=None, tileCol=None): @@ -357,8 +357,8 @@ def get_collection_tiles_data(collection_id=None, tileMatrixSetId=None, return response -@routes.route('/processes') -@routes.route('/processes/') +@BLUEPRINT.route('/processes') +@BLUEPRINT.route('/processes/') def processes(process_id=None): """ OGC API - Processes description endpoint @@ -378,7 +378,7 @@ def processes(process_id=None): return response -@routes.route('/processes//jobs', methods=['GET', 'POST']) +@BLUEPRINT.route('/processes//jobs', methods=['GET', 'POST']) def process_jobs(process_id=None): """ OGC API - Processes jobs endpoint @@ -402,7 +402,7 @@ def process_jobs(process_id=None): return response -@routes.route('/stac') +@BLUEPRINT.route('/stac') def stac_catalog_root(): """ STAC root endpoint @@ -421,7 +421,7 @@ def stac_catalog_root(): return response -@routes.route('/stac/') +@BLUEPRINT.route('/stac/') def stac_catalog_path(path): """ STAC path endpoint @@ -442,7 +442,7 @@ def stac_catalog_path(path): return response -APP.register_blueprint(routes) +APP.register_blueprint(BLUEPRINT) @click.command() diff --git a/pygeoapi/starlette_app.py b/pygeoapi/starlette_app.py index d2e63f1..83f37e0 100644 --- a/pygeoapi/starlette_app.py +++ b/pygeoapi/starlette_app.py @@ -38,7 +38,6 @@ from starlette.staticfiles import StaticFiles from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import Response -from starlette.routing import Route import uvicorn from pygeoapi.api import API @@ -75,18 +74,8 @@ if (OGC_SCHEMAS_LOCATION is not None and api_ = API(CONFIG) -routes = [] - -def _route(path, methods=None): - def inner(func): - app.add_route(path, func, methods=methods) - routes.append(Route(path, func, methods=methods)) - return func - return inner - - -@_route('/') +@app.route('/') async def landing_page(request: Request): """ OGC API landing page endpoint @@ -104,8 +93,8 @@ async def landing_page(request: Request): return response -@_route('/openapi') -@_route('/openapi/') +@app.route('/openapi') +@app.route('/openapi/') async def openapi(request: Request): """ OpenAPI endpoint @@ -126,8 +115,8 @@ async def openapi(request: Request): return response -@_route('/conformance') -@_route('/conformance/') +@app.route('/conformance') +@app.route('/conformance/') async def conformance(request: Request): """ OGC API conformance endpoint @@ -145,10 +134,10 @@ async def conformance(request: Request): return response -@_route('/collections') -@_route('/collections/') -@_route('/collections/{collection_id}') -@_route('/collections/{collection_id}/') +@app.route('/collections') +@app.route('/collections/') +@app.route('/collections/{collection_id}') +@app.route('/collections/{collection_id}/') async def collections(request: Request, collection_id=None): """ OGC API collections endpoint @@ -170,8 +159,8 @@ async def collections(request: Request, collection_id=None): return response -@_route('/collections/{collection_id}/queryables') -@_route('/collections/{collection_id}/queryables/') +@app.route('/collections/{collection_id}/queryables') +@app.route('/collections/{collection_id}/queryables/') async def collection_queryables(request: Request, collection_id=None): """ OGC API collections queryables endpoint @@ -193,8 +182,8 @@ async def collection_queryables(request: Request, collection_id=None): return response -@_route('/collections/{name}/tiles') -@_route('/collections/{name}/tiles/') +@app.route('/collections/{name}/tiles') +@app.route('/collections/{name}/tiles/') async def get_collection_tiles(request: Request, name=None): """ OGC open api collections tiles access point @@ -216,9 +205,9 @@ async def get_collection_tiles(request: Request, name=None): return response -@_route('/collections/{name}/tiles/\ +@app.route('/collections/{name}/tiles/\ {tileMatrixSetId}/{tile_matrix}/{tileRow}/{tileCol}') -@_route('/collections/{name}/tiles/\ +@app.route('/collections/{name}/tiles/\ {tileMatrixSetId}/{tile_matrix}/{tileRow}/{tileCol}/') def get_collection_items_tiles(request: Request, name=None, tileMatrixSetId=None, tile_matrix=None, @@ -256,10 +245,10 @@ def get_collection_items_tiles(request: Request, name=None, return response -@_route('/collections/{collection_id}/items') -@_route('/collections/{collection_id}/items/') -@_route('/collections/{collection_id}/items/{item_id}') -@_route('/collections/{collection_id}/items/{item_id}/') +@app.route('/collections/{collection_id}/items') +@app.route('/collections/{collection_id}/items/') +@app.route('/collections/{collection_id}/items/{item_id}') +@app.route('/collections/{collection_id}/items/{item_id}/') async def collection_items(request: Request, collection_id=None, item_id=None): """ OGC API collections items endpoint @@ -290,7 +279,7 @@ async def collection_items(request: Request, collection_id=None, item_id=None): return response -@_route('/collections//coverage') +@app.route('/collections/{collection_id}/coverage') def collection_coverage(request: Request, collection_id): """ OGC API - Coverages coverage endpoint @@ -300,6 +289,9 @@ def collection_coverage(request: Request, collection_id): :returns: Starlette HTTP Response """ + if 'collection_id' in request.path_params: + collection_id = request.path_params['collection_id'] + headers, status_code, content = api_.get_collection_coverage( request.headers, request.query_params, collection_id) @@ -311,7 +303,7 @@ def collection_coverage(request: Request, collection_id): return response -@_route('/collections//coverage/domainset') +@app.route('/collections/{collection_id}/coverage/domainset') def collection_coverage_domainset(request: Request, collection_id): """ OGC API - Coverages coverage domainset endpoint @@ -321,6 +313,9 @@ def collection_coverage_domainset(request: Request, collection_id): :returns: Starlette HTTP Response """ + if 'collection_id' in request.path_params: + collection_id = request.path_params['collection_id'] + headers, status_code, content = api_.get_collection_coverage_domainset( request.headers, request.query_params, collection_id) @@ -332,7 +327,7 @@ def collection_coverage_domainset(request: Request, collection_id): return response -@_route('/collections//coverage/rangetype') +@app.route('/collections/{collection_id}/coverage/rangetype') def collection_coverage_rangetype(request: Request, collection_id): """ OGC API - Coverages coverage rangetype endpoint @@ -342,6 +337,9 @@ def collection_coverage_rangetype(request: Request, collection_id): :returns: Starlette HTTP Response """ + if 'collection_id' in request.path_params: + collection_id = request.path_params['collection_id'] + headers, status_code, content = api_.get_collection_coverage_rangetype( request.headers, request.query_params, collection_id) @@ -353,10 +351,10 @@ def collection_coverage_rangetype(request: Request, collection_id): return response -@_route('/processes') -@_route('/processes/') -@_route('/processes/{process_id}') -@_route('/processes/{process_id}/') +@app.route('/processes') +@app.route('/processes/') +@app.route('/processes/{process_id}') +@app.route('/processes/{process_id}/') async def processes(request: Request, process_id=None): """ OGC API - Processes description endpoint @@ -376,8 +374,8 @@ async def processes(request: Request, process_id=None): return response -@_route('/processes/{process_id}/jobs', methods=['GET', 'POST']) -@_route('/processes/{process_id}/jobs/', methods=['GET', 'POST']) +@app.route('/processes/{process_id}/jobs', methods=['GET', 'POST']) +@app.route('/processes/{process_id}/jobs/', methods=['GET', 'POST']) async def process_jobs(request: Request, process_id=None): """ OGC API - Processes jobs endpoint @@ -401,7 +399,7 @@ async def process_jobs(request: Request, process_id=None): return response -@_route('/stac') +@app.route('/stac') async def stac_catalog_root(request: Request): """ STAC root endpoint @@ -420,7 +418,7 @@ async def stac_catalog_root(request: Request): return response -@_route('/stac/{path:path}') +@app.route('/stac/{path:path}') async def stac_catalog_path(request: Request): """ STAC endpoint