Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions app/ro_crates/routes/get_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,25 @@
# Copyright (c) 2025 eScience Lab, The University of Manchester

from apiflask import APIBlueprint, Schema
from apiflask.fields import String
from apiflask.fields import String, Boolean
from marshmallow.fields import Nested
from flask import Response

from app.services.validation_service import get_ro_crate_validation_task

get_routes_bp = APIBlueprint("get_routes", __name__)


class MinioConfig(Schema):
endpoint = String(required=True)
accesskey = String(required=True)
secret = String(required=True)
ssl = Boolean(required=True)
bucket = String(required=True)


class ValidateResult(Schema):
minio_bucket = String(required=True)
minio_config = Nested(MinioConfig, required=True)
root_path = String(required=False)


Expand All @@ -28,7 +37,12 @@ def get_ro_crate_validation_by_id(json_data, crate_id) -> tuple[Response, int]:
- **crate_id**: The RO-Crate ID. _Required_.

Request Body Parameters:
- **minio_bucket**: The MinIO bucket containing the RO-Crate. _Required_
- **minio_config**: The MinIO bucket containing the RO-Crate. _Required_
- **endpoint**: Endpoint, e.g. 'localhost:9000'
- **accesskey**: Access key / username
- **secret**: Secret / password
- **ssl**: Use SSL encryption? True/False
- **bucket**: The MinIO bucket to access
- **root_path**: The root path containing the RO-Crate. _Optional_

Returns:
Expand All @@ -38,11 +52,11 @@ def get_ro_crate_validation_by_id(json_data, crate_id) -> tuple[Response, int]:
- KeyError: If required parameters (`crate_id`) are missing.
"""

minio_bucket = json_data["minio_bucket"]
minio_config = json_data["minio_config"]

if "root_path" in json_data:
root_path = json_data["root_path"]
else:
root_path = None

return get_ro_crate_validation_task(minio_bucket, crate_id, root_path)
return get_ro_crate_validation_task(minio_config, crate_id, root_path)
24 changes: 19 additions & 5 deletions app/ro_crates/routes/post_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
# Copyright (c) 2025 eScience Lab, The University of Manchester

from apiflask import APIBlueprint, Schema
from apiflask.fields import String
from apiflask.fields import String, Boolean
from marshmallow.fields import Nested
from flask import Response

from app.services.validation_service import (
Expand All @@ -16,8 +17,16 @@
post_routes_bp = APIBlueprint("post_routes", __name__)


class MinioConfig(Schema):
endpoint = String(required=True)
accesskey = String(required=True)
secret = String(required=True)
ssl = Boolean(required=True)
bucket = String(required=True)


class ValidateCrate(Schema):
minio_bucket = String(required=True)
minio_config = Nested(MinioConfig, required=True)
root_path = String(required=False)
profile_name = String(required=False)
webhook_url = String(required=False)
Expand All @@ -38,7 +47,12 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
- **crate_id**: The RO-Crate ID. _Required_.

Request Body Parameters:
- **minio_bucket**: The MinIO bucket containing the RO-Crate. _Required_
- **minio_config**: The MinIO bucket containing the RO-Crate. _Required_
- **endpoint**: Endpoint, e.g. 'localhost:9000'
- **accesskey**: Access key / username
- **secret**: Secret / password
- **ssl**: Use SSL encryption? True/False
- **bucket**: The MinIO bucket to access
- **root_path**: The root path containing the RO-Crate. _Optional_
- **profile_name**: The profile name for validation. _Optional_.
- **webhook_url**: The webhook URL where validation results will be sent. _Optional_.
Expand All @@ -50,7 +64,7 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
- KeyError: If required parameters (`crate_id` or `webhook_url`) are missing.
"""

minio_bucket = json_data["minio_bucket"]
minio_config = json_data["minio_config"]

if "root_path" in json_data:
root_path = json_data["root_path"]
Expand All @@ -67,7 +81,7 @@ def validate_ro_crate_via_id(json_data, crate_id) -> tuple[Response, int]:
else:
profile_name = None

return queue_ro_crate_validation_task(minio_bucket, crate_id, root_path, profile_name, webhook_url)
return queue_ro_crate_validation_task(minio_config, crate_id, root_path, profile_name, webhook_url)


@post_routes_bp.post("/validate_metadata")
Expand Down
25 changes: 15 additions & 10 deletions app/services/validation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@
)

from app.utils.config import InvalidAPIUsage
from app.utils.minio_utils import get_minio_client


logger = logging.getLogger(__name__)


def queue_ro_crate_validation_task(
minio_bucket, crate_id, root_path=None, profile_name=None, webhook_url=None
minio_config, crate_id, root_path=None, profile_name=None, webhook_url=None
) -> tuple[Response, int]:
"""
Queues an RO-Crate for validation with Celery.

:param minio_bucket: The MinIO bucket containing the RO-Crate.
:param minio_config: Access settings for Minio instance containing the RO-Crate.
:param crate_id: The ID of the RO-Crate to validate.
:param root_path: The root path containing the RO-Crate.
:param profile_name: The profile to validate against.
Expand All @@ -39,16 +40,18 @@ def queue_ro_crate_validation_task(
"""

logging.info(f"Processing: {crate_id}, {profile_name}, {webhook_url}")
logging.info(f"Minio Bucket: {minio_bucket}; Root path: {root_path}")
logging.info(f"Minio Bucket: {minio_config['bucket']}; Root path: {root_path}")

if check_ro_crate_exists(minio_bucket, crate_id, root_path):
minio_client = get_minio_client(minio_config)

if check_ro_crate_exists(minio_client, minio_config["bucket"], crate_id, root_path):
logging.info("RO-Crate exists")
else:
logging.info("RO-Crate does not exist")
raise InvalidAPIUsage(f"No RO-Crate with prefix: {crate_id}", 400)

try:
process_validation_task_by_id.delay(minio_bucket, crate_id, root_path, profile_name, webhook_url)
process_validation_task_by_id.delay(minio_config, crate_id, root_path, profile_name, webhook_url)
return jsonify({"message": "Validation in progress"}), 202

except Exception as e:
Expand Down Expand Up @@ -97,31 +100,33 @@ def queue_ro_crate_metadata_validation_task(


def get_ro_crate_validation_task(
minio_bucket: str,
minio_config: dict,
crate_id: str,
root_path: str,
) -> tuple[Response, int]:
"""
Retrieves an RO-Crate validation result.

:param minio_bucket: The MinIO bucket containing the RO-Crate.
:param minio_config: Access settings for Minio instance containing the RO-Crate.
:param crate_id: The ID of the RO-Crate to validate.
:param root_path: The root path containing the RO-Crate.
:return: A tuple containing a JSON response and an HTTP status code.
:raises Exception: If an error occurs whilst retreiving validation result
"""
logging.info(f"Retrieving validation for: {crate_id}")

if check_ro_crate_exists(minio_bucket, crate_id, root_path):
minio_client = get_minio_client(minio_config)

if check_ro_crate_exists(minio_client, minio_config["bucket"], crate_id, root_path):
logging.info("RO-Crate exists")
else:
logging.info("RO-Crate does not exist")
raise InvalidAPIUsage(f"No RO-Crate with prefix: {crate_id}", 400)

if check_validation_exists(minio_bucket, crate_id, root_path):
if check_validation_exists(minio_client, minio_config["bucket"], crate_id, root_path):
logging.info("Validation result exists")
else:
logging.info("Validation does not exist")
raise InvalidAPIUsage(f"No validation result yet for RO-Crate: {crate_id}", 400)

return return_ro_crate_validation(minio_bucket, crate_id, root_path), 200
return return_ro_crate_validation(minio_client, minio_config["bucket"], crate_id, root_path), 200
22 changes: 14 additions & 8 deletions app/tasks/validation_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@

@celery.task
def process_validation_task_by_id(
minio_bucket: str, crate_id: str, root_path: str, profile_name: str | None, webhook_url: str | None
minio_config: dict, crate_id: str, root_path: str, profile_name: str | None, webhook_url: str | None
) -> None:
"""
Background task to process the RO-Crate validation by ID.

:param minio_bucket: The MinIO bucket containing the RO-Crate.
:param minio_config: The MinIO configuration.
:param crate_id: The ID of the RO-Crate to validate.
:param root_path: The root path containing the RO-Crate.
:param profile_name: The name of the validation profile to use. Defaults to None.
Expand All @@ -45,11 +45,13 @@ def process_validation_task_by_id(

# TODO: Split try statements: (1) fetch and validate; (2) write to minio; (3) webhook

minio_client = get_minio_client(minio_config)

file_path = None

try:
# Fetch the RO-Crate from MinIO using the provided ID:
file_path = fetch_ro_crate_from_minio(minio_bucket, crate_id, root_path)
file_path = fetch_ro_crate_from_minio(minio_client, minio_config["bucket"], crate_id, root_path)

logging.info(f"Processing validation task for {file_path}")

Expand All @@ -67,7 +69,7 @@ def process_validation_task_by_id(
logging.info(f"RO Crate {crate_id} is invalid.")

# Update the validation status in MinIO:
update_validation_status_in_minio(minio_bucket, crate_id, root_path, validation_result.to_json())
update_validation_status_in_minio(minio_client, minio_config["bucket"], crate_id, root_path, validation_result.to_json())

# TODO: Prepare the data to send to the webhook, and send the webhook notification.

Expand Down Expand Up @@ -192,36 +194,39 @@ def perform_ro_crate_validation(


def check_ro_crate_exists(
minio_client: object,
bucket_name: str,
crate_id: str,
root_path: str,
) -> bool:
"""
Checks for the existence of an RO-Crate using the provided Crate ID.

:param minio_bucket: The MinIO bucket containing the RO-Crate.
:param minio_client: The MinIO client
:param bucket_name: The MinIO bucket containing the RO-Crate.
:param crate_id: The ID of the RO-Crate to validate.
:param root_path: The root path containing the RO-Crate.
:return: Boolean indicating existence
"""

logging.info(f"Checking for existence of RO-Crate {crate_id}")

minio_client = get_minio_client()
if find_rocrate_object_on_minio(crate_id, minio_client, bucket_name, root_path):
return True
else:
return False


def check_validation_exists(
minio_client: object,
bucket_name: str,
crate_id: str,
root_path: str,
) -> bool:
"""
Checks for the existence of a validation result using the provided Crate ID.

:param minio_client: The MinIO client
:param minio_bucket: The MinIO bucket containing the RO-Crate.
:param crate_id: The ID of the RO-Crate to validate.
:param root_path: The root path containing the RO-Crate.
Expand All @@ -230,25 +235,26 @@ def check_validation_exists(

logging.info(f"Checking for existence of RO-Crate {crate_id}")

minio_client = get_minio_client()
if find_validation_object_on_minio(crate_id, minio_client, bucket_name, root_path):
return True
else:
return False


def return_ro_crate_validation(
minio_client: object,
bucket_name: str,
crate_id: str,
root_path: str,
) -> dict | str:
"""
Retrieves the validation result for an RO-Crate using the provided Crate ID.

:param minio_client: The MinIO client
:param crate_id: The ID of the RO-Crate that has been validated
:return: The validation result
"""

logging.info(f"Fetching validation result for RO-Crate {crate_id}")

return get_validation_status_from_minio(bucket_name, crate_id, root_path)
return get_validation_status_from_minio(minio_client, bucket_name, crate_id, root_path)
Loading