Skip to content
Draft
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
1,856 changes: 1,768 additions & 88 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@
"tailwindcss-animate": "^1.0.7",
"use-debounce": "^10.0.2",
"uuid": "^11.0.3",
"vite-plugin-node-polyfills": "^0.24.0",
"wordcloud": "^1.2.2",
"zarrita": "^0.5.2",
"zod": "^3.25.49",
"zustand": "^5.0.6"
},
Expand Down
10 changes: 10 additions & 0 deletions python/mdvtools/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Metadata extraction and processing module for MDV.

This module provides functionality for fetching and processing metadata
from various dataset formats including Zarr stores and SpatialData datasets.
"""

from .routes import register_metadata_routes

__all__ = ['register_metadata_routes']
111 changes: 111 additions & 0 deletions python/mdvtools/metadata/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
Flask routes for metadata extraction functionality.

This module contains all the route handlers for fetching metadata from
various dataset formats including Zarr stores and SpatialData datasets.
"""

import os
import asyncio
from flask import request, jsonify
from mdvtools.zarr_utils import get_zarr_extractor
from mdvtools.logging_config import get_logger

logger = get_logger(__name__)

def register_metadata_routes(project_bp, project):
"""
Register metadata-related routes with the project blueprint.

Args:
project_bp: The Flask blueprint to register routes with
project: The MDVProject instance
"""

@project_bp.route("/get_metadata", methods=["GET"])
def get_metadata():
"""
Fetch metadata from a dataset URL (supports Zarr and SpatialData formats).

Query Parameters:
url: The URL of the dataset to fetch metadata from

Returns:
JSON response with metadata structure or error message
"""
logger.info("=== METADATA REQUEST ===")
logger.info(f"Request method: {request.method}")
logger.info(f"Request path: {request.path}")
logger.info(f"Request args: {request.args}")
logger.info(f"Project: {project.id}")

dataset_url = None
try:
dataset_url = request.args.get('url')
logger.info(f"Extracted dataset_url: {dataset_url}")

if not dataset_url:
logger.info("ERROR: Missing 'url' parameter")
return jsonify({
"error": "Missing 'url' parameter",
"message": "Please provide a 'url' query parameter with the dataset URL"
}), 400

# Validate URL format
if not dataset_url.startswith(('http://', 'https://')):
logger.info(f"ERROR: Invalid URL format: {dataset_url}")
return jsonify({
"error": "Invalid URL format",
"message": "URL must start with http:// or https://"
}), 400

# Convert localhost to host.docker.internal if running in Docker
if 'localhost' in dataset_url and os.path.exists('/.dockerenv'):
dataset_url = dataset_url.replace('localhost', 'host.docker.internal')
logger.info(f"Running in Docker, converted URL to: {dataset_url}")

logger.info(f"URL validation passed, fetching metadata for: {dataset_url}")

# Extract metadata using zarr utilities (supports both Zarr and SpatialData)
logger.info("Getting metadata extractor...")
extractor = get_zarr_extractor()
logger.info(f"Metadata extractor: {extractor}")

logger.info("Calling fetch_zarr_metadata...")
print("Creating new event loop for async metadata call...")
# Run the async function in the current event loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
print("Running fetch_zarr_metadata in event loop...")
metadata = loop.run_until_complete(extractor.fetch_zarr_metadata(dataset_url))
print(f"Event loop completed, metadata type: {type(metadata)}")
except Exception as loop_e:
print(f"Exception in event loop: {loop_e}")
raise loop_e
finally:
print("Closing event loop...")
loop.close()
print("Event loop closed")
logger.info(f"Metadata extraction successful, keys: {list(metadata.keys()) if isinstance(metadata, dict) else type(metadata)}")
print(f"Metadata extraction successful, keys: {list(metadata.keys()) if isinstance(metadata, dict) else type(metadata)}")

response_data = {
"success": True,
"metadata": metadata,
"url": dataset_url
}
logger.info("Returning successful response")
return jsonify(response_data)

except Exception as e:
logger.info(f"EXCEPTION in get_metadata: {str(e)}")
logger.info(f"Exception type: {type(e)}")
import traceback
logger.info(f"Full traceback: {traceback.format_exc()}")

return jsonify({
"error": "Metadata extraction failed",
"message": str(e),
"url": dataset_url
}), 500
6 changes: 6 additions & 0 deletions python/mdvtools/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
from datetime import datetime
from typing import Optional
import threading
import asyncio
import scanpy as sc
from mdvtools.conversions import convert_scanpy_to_mdv
from mdvtools.server_extension import MDVServerOptions
from mdvtools.logging_config import get_logger
from mdvtools.metadata import register_metadata_routes


logger = get_logger(__name__)
Expand Down Expand Up @@ -101,6 +103,9 @@ def create_app(
for extension in options.extensions:
extension.register_routes(project, project_bp)

# Register metadata routes
register_metadata_routes(project_bp, project)


@project_bp.route("/")
def project_index():
Expand Down Expand Up @@ -154,6 +159,7 @@ def get_json_file(file: str):
return f"Problem parsing state file: {e}", 500
return send_file(path)


# gets the raw byte data and packages it in the correct response
@project_bp.route("/get_data", methods=["POST"])
def get_data():
Expand Down
Loading