Skip to content

Commit

Permalink
Refactor to use FastAPI (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
petechd authored Oct 15, 2024
1 parent 268e62c commit 2bba332
Show file tree
Hide file tree
Showing 11 changed files with 564 additions and 440 deletions.
9 changes: 5 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ RUN poetry install --only main

EXPOSE 5000

ENV FLASK_APP=api.py

ENTRYPOINT flask run --host 0.0.0.0

CMD ["gunicorn", "api:app", \
"--bind", "0.0.0.0:5000", \
"--workers", "20", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--timeout", "0"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ start-ajv:
npm run start

run: start-ajv
poetry run ./scripts/run_app.sh
poetry run python api.py

lint: lint-python
npm run lint
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ To run the app:
```
make run
```
If you want to run the app locally using multiple server workers set reload to "False" in the Uvicorn settings in api.py:
```python
uvicorn.run("api:app", workers=20, port=5001, reload=False)
```
The validator can be called directly in the browser using the "/validate" endpoint and the "url" parameter for the address where the schema is located (eg. GitHub Gist raw json):
```
http://localhost:5001/validate?url=...
```

## Testing

Expand Down
95 changes: 88 additions & 7 deletions api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,92 @@
from flask import Flask
import json
import os
from json import JSONDecodeError
from urllib import error, request

from app.views.status import status_blueprint
from app.views.validate import validate_blueprint
import requests
import uvicorn
from fastapi import Body, FastAPI, Response
from requests import RequestException
from structlog import get_logger

from app.validators.questionnaire_validator import QuestionnaireValidator

app = FastAPI()


@app.get("/status")
async def status():
return Response(status_code=200)


logger = get_logger()

AJV_HOST = os.getenv("AJV_HOST", "localhost")

AJV_VALIDATOR_URL = f"http://{AJV_HOST}:5002/validate"


@app.post("/validate")
async def validate_schema_request_body(payload=Body(None)):
logger.info("Validating schema")
return await validate_schema(payload)


@app.get("/validate")
async def validate_schema_from_url(url=None):
if url:
logger.info("Validating schema from URL", url=url)
try:
with request.urlopen(url) as opened_url:
return await validate_schema(data=opened_url.read().decode())
except error.URLError:
return Response(
status_code=404, content=f"Could not load schema at URL [{url}]"
)


async def validate_schema(data):
json_to_validate = None
if data:
if isinstance(data, str):
try:
json_to_validate = json.loads(data)
except JSONDecodeError:
logger.info("Could not parse JSON", status=400)
return Response(status_code=400, content="Could not parse JSON")
elif isinstance(data, dict):
json_to_validate = data

response = {}
try:
ajv_response = requests.post(
AJV_VALIDATOR_URL, json=json_to_validate, timeout=10
)
if ajv_response_dict := ajv_response.json():
response["errors"] = ajv_response_dict["errors"]
logger.info("Schema validator returned errors", status=400)
return response, 400

except RequestException:
logger.info("AJV Schema validator service unavailable")
return json.dumps(obj={}, error="AJV Schema validator service unavailable")

validator = QuestionnaireValidator(json_to_validate)
validator.validate()

if validator.errors:
response["errors"] = validator.errors
logger.info("Questionnaire validator returned errors", status=400)
response = Response(content=json.dumps(response), status_code=400)

return response

logger.info("Schema validation passed", status=200)

response = Response(content=json.dumps(response), status_code=200)

return response

application = Flask(__name__)
application.register_blueprint(validate_blueprint)
application.register_blueprint(status_blueprint)

if __name__ == "__main__":
application.run(port=5001)
uvicorn.run("api:app", workers=20, port=5001, reload=True)
2 changes: 1 addition & 1 deletion app/validators/value_source_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ValueSourceValidator(Validator):
}
RESPONSE_METADATA_IDENTIFIERS = ["started_at"]

def __init__(
def __init__( # pylint: disable=too-many-positional-arguments
self,
value_source,
json_path,
Expand Down
Empty file removed app/views/__init__.py
Empty file.
8 changes: 0 additions & 8 deletions app/views/status.py

This file was deleted.

78 changes: 0 additions & 78 deletions app/views/validate.py

This file was deleted.

Loading

0 comments on commit 2bba332

Please sign in to comment.