Skip to content

Commit

Permalink
Update CustomRequestBodyValidator for Connexion 3 and re-enable its test
Browse files Browse the repository at this point in the history
  • Loading branch information
juhoinkinen committed Apr 8, 2024
1 parent f12ca5e commit 3703423
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 36 deletions.
20 changes: 13 additions & 7 deletions annif/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ def create_flask_app(config_name: str | None = None) -> Flask:
def create_cx_app(config_name: str | None = None) -> FlaskApp:
"""Create a Connexion app to be used for the API."""
import connexion
from connexion.datastructures import MediaTypeDict
from connexion.middleware import MiddlewarePosition
from connexion.validators import FormDataValidator, MultiPartFormDataValidator
from starlette.middleware.cors import CORSMiddleware

# from flask_cors import CORS # TODO Use CORSMiddleware
import annif.registry

# from annif.openapi.validation import CustomRequestBodyValidator # TODO Re-enable
from annif.openapi.validation import CustomRequestBodyValidator

specdir = os.path.join(os.path.dirname(__file__), "openapi")
cxapp = connexion.FlaskApp(__name__, specification_dir=specdir)
Expand All @@ -50,10 +50,16 @@ def create_cx_app(config_name: str | None = None) -> FlaskApp:
cxapp.app.config.from_object(config_name)
cxapp.app.config.from_envvar("ANNIF_SETTINGS", silent=True)

# validator_map = {
# "body": CustomRequestBodyValidator,
# }
cxapp.add_api("annif.yaml") # validator_map=validator_map)
validator_map = {
"body": MediaTypeDict(
{
"*/*json": CustomRequestBodyValidator,
"application/x-www-form-urlencoded": FormDataValidator,
"multipart/form-data": MultiPartFormDataValidator,
}
),
}
cxapp.add_api("annif.yaml", validator_map=validator_map)

# add CORS support
cxapp.add_middleware(
Expand Down
35 changes: 13 additions & 22 deletions annif/openapi/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,39 @@
from __future__ import annotations

import logging
from typing import Any

import jsonschema
from connexion import decorators
from connexion.exceptions import BadRequestProblem
from connexion.utils import is_null
from connexion.json_schema import format_error_with_path
from connexion.validators import JSONRequestBodyValidator
from jsonschema.exceptions import ValidationError

logger = logging.getLogger("openapi.validation")


class CustomRequestBodyValidator(decorators.validation.RequestBodyValidator):
class CustomRequestBodyValidator(JSONRequestBodyValidator):
"""Custom request body validator that overrides the default error message for the
'maxItems' validator for the 'documents' property."""

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

def validate_schema(
self,
data: list | dict,
url: str,
) -> None:
"""Validate the request body against the schema."""

if self.is_null_value_valid and is_null(data):
return None # pragma: no cover

def _validate(self, body: Any) -> dict | None:
if not self._nullable and body is None:
raise BadRequestProblem("Request body must not be empty")

Check warning on line 25 in annif/openapi/validation.py

View check run for this annotation

Codecov / codecov/patch

annif/openapi/validation.py#L25

Added line #L25 was not covered by tests
try:
self.validator.validate(data)
except jsonschema.ValidationError as exception:
return self._validator.validate(body)
except ValidationError as exception:
# Prevent logging request body with contents of all documents
if exception.validator == "maxItems" and list(exception.schema_path) == [
"properties",
"documents",
"maxItems",
]:
exception.message = "too many items"

error_path_msg = self._error_path_message(exception=exception)
error_path_msg = format_error_with_path(exception=exception)
logger.error(
"{url} validation error: {error}{error_path_msg}".format(
url=url, error=exception.message, error_path_msg=error_path_msg
),
f"Validation error: {exception.message}{error_path_msg}",
extra={"validator": "body"},
)
raise BadRequestProblem(detail=f"{exception.message}{error_path_msg}")
return None
14 changes: 7 additions & 7 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ def test_openapi_suggest_batch(app_client):
assert body[0]["results"][0]["label"] == "dummy-fi"


# def test_openapi_suggest_batch_too_many_documents(app_client):
# data = {"documents": [{"text": "A quick brown fox jumped over the lazy dog."}]*33}
# req = app_client.post(
# "http://localhost:8000/v1/projects/dummy-fi/suggest-batch", json=data
# )
# assert req.status_code == 400
# assert req.json()["detail"] == "too many items - 'documents'"
def test_openapi_suggest_batch_too_many_documents(app_client):
data = {"documents": [{"text": "A quick brown fox jumped over the lazy dog."}] * 33}
req = app_client.post(
"http://localhost:8000/v1/projects/dummy-fi/suggest-batch", json=data
)
assert req.status_code == 400
assert req.json()["detail"] == "too many items - 'documents'"


def test_openapi_learn(app_client):
Expand Down

0 comments on commit 3703423

Please sign in to comment.