-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #49 from Dewberry/refactor
Refactor
- Loading branch information
Showing
65 changed files
with
2,142 additions
and
2,449 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Do not put secrets in here! This file is in the repo. | ||
|
||
FLASK_APP=api.app | ||
FLASK_DEBUG=0 | ||
FLASK_RUN_RELOAD=0 | ||
FLASK_RUN_HOST=0.0.0.0 | ||
FLASK_RUN_PORT=80 | ||
|
||
# Do not put secrets in here! This file is in the repo. |
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,69 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
permissions: | ||
contents: read | ||
|
||
concurrency: | ||
group: ${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
build: | ||
|
||
strategy: | ||
matrix: | ||
python-version: | ||
- "3.9" | ||
- "3.10" | ||
- "3.11" | ||
- "3.12" | ||
os: ["windows-latest"] | ||
|
||
runs-on: ${{ matrix.os }} | ||
environment: ci-env | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install ".[dev]" | ||
# - name: Lint (ruff) | ||
# run: | | ||
# ruff check | ||
# ruff format --check | ||
|
||
- name: Run tests with coverage | ||
run: | | ||
pytest tests/conflation_tests.py | ||
pytest tests/read_ras_files_tests.py | ||
# - name: Run tests with coverage | ||
# run: | | ||
# pytest --cov --cov-report term-missing | ||
|
||
# - name: Run tests with coverage | ||
# run: | | ||
# pytest --cov=src --cov-report term-missing | ||
|
||
# - name: Run coverage | ||
# run: | | ||
# coverage xml | ||
|
||
# - name: Upload coverage reports to Codecov | ||
# uses: codecov/[email protected] | ||
# with: | ||
# token: ${{ secrets.CODECOV_TOKEN }} |
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,9 @@ | ||
repos: | ||
- repo: https://github.com/astral-sh/ruff-pre-commit | ||
# Ruff version. | ||
rev: v0.4.2 | ||
hooks: | ||
# Run the linter. | ||
- id: ruff | ||
# Run the formatter. | ||
- id: ruff-format |
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,2 @@ | ||
[FORMAT] | ||
max-line-length=120 |
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,34 @@ | ||
@echo off | ||
|
||
set thread_count=1 | ||
|
||
:: Identify the full path to huey_consumer.py (ships with huey library) | ||
set huey_consumer_full_path= | ||
for /f "delims=" %%i in ('where huey_consumer.py') do set huey_consumer_full_path=%%i | ||
|
||
:: Set up logs dir | ||
set logs_dir="api\logs\" | ||
echo "Deleting logs dir if exists: %logs_dir%" | ||
rmdir /s /q %logs_dir% | ||
if exist %logs_dir% ( | ||
echo Error: could not delete %logs_dir% | ||
echo Press any key to exit. | ||
set /p input= | ||
exit /b 1 | ||
) | ||
echo "Creating logs dir: %logs_dir%" | ||
if not exist %logs_dir% mkdir %logs_dir% | ||
if %errorlevel% neq 0 ( | ||
echo Error: could not create %logs_dir% | ||
echo Press any key to exit. | ||
set /p input= | ||
exit /b 1 | ||
) | ||
|
||
:: Launch huey consumer in separate terminal | ||
echo "Starting ripple-huey" | ||
start "ripple-huey" cmd /k "python -u %huey_consumer_full_path% api.tasks.huey -w %thread_count%" | ||
|
||
:: Launch Flask app in a separate terminal | ||
echo "Starting ripple-flask" | ||
start "ripple-flask" cmd /k "flask run" |
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,10 @@ | ||
@echo off | ||
|
||
set api_test_script="api\run_api_test.py" | ||
echo Running script: %api_test_script% | ||
|
||
python -u "%api_test_script%" | ||
|
||
if %errorlevel% neq 0 (echo Error above with code %errorlevel%.) else (echo Success.) | ||
echo Press enter to exit this test terminal. | ||
set /p input= |
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,132 @@ | ||
from __future__ import annotations | ||
|
||
from http import HTTPStatus | ||
import time | ||
import traceback | ||
|
||
from flask import Flask, Response, jsonify, request | ||
from werkzeug.exceptions import BadRequest | ||
|
||
from api import tasks | ||
from api.utils import get_unexpected_and_missing_args | ||
|
||
app = Flask(__name__) | ||
|
||
|
||
@app.route("/ping", methods=["GET"]) | ||
def ping(): | ||
return jsonify({"status": "healthy"}), HTTPStatus.OK | ||
|
||
|
||
@app.route("/processes/test/execution", methods=["POST"]) | ||
def test(): | ||
response, http_status = process_async_request(tasks.noop) | ||
if http_status != HTTPStatus.CREATED: | ||
return jsonify(response.json, HTTPStatus.INTERNAL_SERVER_ERROR) | ||
|
||
timeout_seconds = 10 | ||
|
||
start = time.time() | ||
while time.time() - start < timeout_seconds: | ||
time.sleep(0.2) | ||
status = tasks.status(response.json["jobID"]) | ||
if status == "failed": | ||
return jsonify({"status": "not healthy"}), HTTPStatus.INTERNAL_SERVER_ERROR | ||
if status == "successful": | ||
return jsonify({"status": "healthy"}), HTTPStatus.OK | ||
return ( | ||
jsonify({"status": f"huey is busy or not active, ping timed out after {timeout_seconds} seconds"}), | ||
HTTPStatus.GATEWAY_TIMEOUT, | ||
) | ||
|
||
|
||
@app.route("/jobs/<task_id>", methods=["GET"]) | ||
def task_status(task_id): | ||
# https://developer.ogc.org/api/processes/index.html#tag/Status | ||
status = tasks.status(task_id) | ||
if status == "accepted": | ||
return jsonify({"type": "process", "jobID": task_id, "status": status}), HTTPStatus.OK | ||
if status == "running": | ||
return jsonify({"type": "process", "jobID": task_id, "status": status}), HTTPStatus.OK | ||
if status == "successful": | ||
return ( | ||
jsonify({"type": "process", "jobID": task_id, "status": status, "detail": tasks.result(task_id)}), | ||
HTTPStatus.OK, | ||
) | ||
if status == "failed": | ||
return ( | ||
jsonify({"type": "process", "jobID": task_id, "status": status, "detail": tasks.result(task_id)}), | ||
HTTPStatus.OK, | ||
) | ||
if status == "dismissed": | ||
return ( | ||
jsonify({"type": "process", "jobID": task_id, "status": status, "detail": tasks.result(task_id)}), | ||
HTTPStatus.OK, | ||
) | ||
if status == "notfound": | ||
return jsonify({"type": "process", "detail": f"job ID not found: {task_id}"}), HTTPStatus.NOT_FOUND | ||
return jsonify({"type": "process", "detail": f"unexpected status: {status}"}), HTTPStatus.INTERNAL_SERVER_ERROR | ||
|
||
|
||
@app.route("/jobs/<task_id>/results", methods=["GET"]) | ||
def task_result(task_id): | ||
# https://developer.ogc.org/api/processes/index.html#tag/Result | ||
try: | ||
status = tasks.status(task_id) | ||
if status == "notfound": | ||
return jsonify({"type": "process", "detail": f"job ID not found: {task_id}"}), HTTPStatus.NOT_FOUND | ||
else: | ||
return jsonify({"type": "process", "detail": tasks.result(task_id)}), HTTPStatus.OK | ||
except: | ||
return jsonify({"type": "process", "detail": f"failed to fetch results"}), HTTPStatus.INTERNAL_SERVER_ERROR | ||
|
||
|
||
@app.route("/jobs/<task_id>/dismiss", methods=["DELETE"]) | ||
def dismiss(task_id): | ||
# https://developer.ogc.org/api/processes/index.html#tag/Dismiss | ||
try: | ||
status = tasks.status(task_id) | ||
if status == "notfound": | ||
return jsonify({"type": "process", "detail": f"job ID not found: {task_id}"}), HTTPStatus.NOT_FOUND | ||
else: | ||
result = tasks.revoke(task_id) | ||
return jsonify({"type": "process", "detail": result}), HTTPStatus.OK | ||
except: | ||
return ( | ||
jsonify({"type": "process", "detail": f"failed to dismiss job ID: {task_id}"}), | ||
HTTPStatus.INTERNAL_SERVER_ERROR, | ||
) | ||
|
||
|
||
def process_async_request(func: callable) -> tuple[Response, HTTPStatus]: | ||
"""Start the execution of the provided func using kwargs from the request body (assume body is a JSON dictionary)""" | ||
# https://developer.ogc.org/api/processes/index.html#tag/Execute/operation/execute | ||
try: | ||
kwargs = request.json # can throw BadRequest when parsing body into json | ||
if not isinstance(kwargs, dict): | ||
raise BadRequest | ||
except BadRequest: | ||
return ( | ||
jsonify({"type": "process", "detail": "could not parse body to json dict"}), | ||
HTTPStatus.BAD_REQUEST, | ||
) | ||
|
||
unexpected, missing = get_unexpected_and_missing_args(func, set(kwargs)) | ||
if unexpected or missing: | ||
return ( | ||
jsonify({"type": "process", "detail": f"unexpected args: {unexpected}, missing args: {missing}"}), | ||
HTTPStatus.BAD_REQUEST, | ||
) | ||
|
||
try: | ||
# tasks.process must be a huey TaskWrapper (decorated by @huey.task). Returns a huey Result object immediately. | ||
result = tasks.process(func, kwargs) | ||
except: | ||
msg = f"unable to submit task: {traceback.format_exc()}" | ||
app.logger.error(msg) | ||
return jsonify({"type": "process", "detail": msg}), HTTPStatus.INTERNAL_SERVER_ERROR | ||
else: | ||
return ( | ||
jsonify({"type": "process", "jobID": result.id, "status": "accepted"}), | ||
HTTPStatus.CREATED, | ||
) |
Oops, something went wrong.