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
85 changes: 85 additions & 0 deletions backend/api/app/api/v1/links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""API endpoints for assemblies and organisms links."""

from fastapi import APIRouter, Depends, HTTPException

from app.core.cache import CacheService, CacheTTL
from app.core.dependencies import get_cache_service
from app.services.links_service import LinksService

router = APIRouter()


@router.get("/assemblies/links")
async def get_assemblies_links(cache: CacheService = Depends(get_cache_service)):
"""Get all assembly links for cross-referencing."""
cache_key = "v1:assemblies:links"

cached = await cache.get(cache_key)
if cached is not None:
return cached

service = LinksService()
response = service.get_assemblies_links()
await cache.set(cache_key, response, ttl=CacheTTL.ONE_DAY)

return response


@router.get("/assemblies/links/{accession}")
async def get_assembly_link(
accession: str, cache: CacheService = Depends(get_cache_service)
):
"""Get a single assembly link by accession."""
cache_key = f"v1:assemblies:links:{accession}"

cached = await cache.get(cache_key)
if cached is not None:
return cached

service = LinksService()
result = service.get_assembly_link(accession)

if result is None:
raise HTTPException(status_code=404, detail=f"Assembly {accession} not found")

await cache.set(cache_key, result, ttl=CacheTTL.ONE_DAY)

return result


@router.get("/organisms/links")
async def get_organisms_links(cache: CacheService = Depends(get_cache_service)):
"""Get all organism links for cross-referencing."""
cache_key = "v1:organisms:links"

cached = await cache.get(cache_key)
if cached is not None:
return cached

service = LinksService()
response = service.get_organisms_links()
await cache.set(cache_key, response, ttl=CacheTTL.ONE_DAY)

return response


@router.get("/organisms/links/{taxon_id}")
async def get_organism_link(
taxon_id: int, cache: CacheService = Depends(get_cache_service)
):
"""Get a single organism link by NCBI taxonomy ID."""
cache_key = f"v1:organisms:links:{taxon_id}"

cached = await cache.get(cache_key)
if cached is not None:
return cached

service = LinksService()
result = service.get_organism_link(taxon_id)

if result is None:
raise HTTPException(status_code=404, detail=f"Organism {taxon_id} not found")

await cache.set(cache_key, result, ttl=CacheTTL.ONE_DAY)

return result
41 changes: 0 additions & 41 deletions backend/api/app/api/v1/ncbi_links.py

This file was deleted.

5 changes: 3 additions & 2 deletions backend/api/app/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.v1 import cache, health, ncbi_links, version
from app.api.v1 import cache, health, links, version
from app.core.config import get_settings

settings = get_settings()

app = FastAPI(
title="BRC Analytics API",
version=settings.APP_VERSION,
openapi_url="/api/openapi.json",
docs_url="/api/docs",
redoc_url="/api/redoc",
)
Expand All @@ -25,7 +26,7 @@
app.include_router(health.router, prefix="/api/v1", tags=["health"])
app.include_router(cache.router, prefix="/api/v1/cache", tags=["cache"])
app.include_router(version.router, prefix="/api/v1/version", tags=["version"])
app.include_router(ncbi_links.router, prefix="/api/v1/links", tags=["ncbi-links"])
app.include_router(links.router, prefix="/api/v1", tags=["links"])


@app.get("/")
Expand Down
98 changes: 98 additions & 0 deletions backend/api/app/services/links_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Service for generating link data from BRC Analytics catalog."""

import json
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional

from app.core.config import get_settings

logger = logging.getLogger(__name__)


class LinksService:
"""Service to generate link files for cross-referencing."""

def __init__(self, catalog_path: str | None = None):
settings = get_settings()
self.catalog_path = Path(catalog_path or settings.CATALOG_PATH)

def _load_json_file(self, filename: str) -> List[Dict[str, Any]]:
file_path = self.catalog_path / filename
try:
with open(file_path, "r") as f:
return json.load(f)
except FileNotFoundError:
logger.error(f"Catalog file not found: {file_path}")
return []
except json.JSONDecodeError as e:
logger.error(f"Error parsing JSON from {file_path}: {e}")
return []

def _build_assembly_link(self, accession: str) -> Dict[str, str]:
"""Build a single assembly link dict."""
url_accession = accession.replace(".", "_")
return {
"assemblyAccession": accession,
"relativePath": f"/data/assemblies/{url_accession}",
}

def _build_organism_link(self, taxonomy_id: int) -> Dict[str, Any]:
"""Build a single organism link dict."""
return {
"ncbiTaxonomyId": taxonomy_id,
"relativePath": f"/data/organisms/{taxonomy_id}",
}

def get_assemblies_links(self) -> Dict[str, Any]:
"""Get all assembly links in v1 format."""
assemblies = self._load_json_file("assemblies.json")
links = []

for assembly in assemblies:
accession = assembly.get("accession")
if not accession:
continue
links.append(self._build_assembly_link(accession))

logger.info(f"Generated {len(links)} assembly links")
return {
"assemblies": links,
}

def get_assembly_link(self, accession: str) -> Optional[Dict[str, str]]:
"""Get a single assembly link by accession."""
assemblies = self._load_json_file("assemblies.json")

for assembly in assemblies:
if assembly.get("accession") == accession:
return self._build_assembly_link(accession)

return None

def get_organisms_links(self) -> Dict[str, Any]:
"""Get all organism links in v1 format."""
organisms = self._load_json_file("organisms.json")
links = []

for org in organisms:
taxonomy_id = org.get("ncbiTaxonomyId")
if not taxonomy_id:
continue
links.append(self._build_organism_link(int(taxonomy_id)))

logger.info(f"Generated {len(links)} organism links")
return {
"organisms": links,
}

def get_organism_link(self, taxon_id: int) -> Optional[Dict[str, Any]]:
"""Get a single organism link by NCBI taxonomy ID."""
organisms = self._load_json_file("organisms.json")

for org in organisms:
taxonomy_id = org.get("ncbiTaxonomyId")
if taxonomy_id is not None and int(taxonomy_id) == taxon_id:
return self._build_organism_link(taxon_id)

return None
74 changes: 0 additions & 74 deletions backend/api/app/services/ncbi_links_service.py

This file was deleted.

Loading
Loading