Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/get actual data from AI Assets #175

Merged
merged 8 commits into from
Nov 9, 2023
99 changes: 99 additions & 0 deletions src/routers/resource_ai_asset_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from fastapi.responses import Response
from httpx import AsyncClient
from typing import Literal
from fastapi import APIRouter, HTTPException, status
from sqlalchemy.engine import Engine

from .resource_router import ResourceRouter, _wrap_as_http_exception


class ResourceAIAssetRouter(ResourceRouter):
def create(self, engine: Engine, url_prefix: str) -> APIRouter:
version = f"v{self.version}"
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
default_kwargs = {
"response_model_exclude_none": True,
"deprecated": self.deprecated_from is not None,
"tags": [self.resource_name_plural],
}

router = self._create(engine, url_prefix)
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved

router.add_api_route(
path=f"{url_prefix}/{self.resource_name_plural}/{version}/{{identifier}}/data",
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
endpoint=self.get_resource_data_func(engine, default=True),
name=self.resource_name,
response_model=str,
**default_kwargs,
)

router.add_api_route(
path=f"{url_prefix}/{self.resource_name_plural}/{version}/{{identifier}}/data/"
f"{{distribution_idx}}",
endpoint=self.get_resource_data_func(engine, default=False),
name=self.resource_name,
response_model=str,
**default_kwargs,
)

return router

def get_resource_data_func(self, engine: Engine, default: bool):
"""
Returns a function to download the actual data from resources.
This function returns a function (instead of being that function directly) because the
docstring and the variables are dynamic, and used in Swagger.
"""

async def get_resource_data(
identifier: str,
distribution_idx: int,
schema: Literal[tuple(self._possible_schemas)] = "aiod", # type:ignore
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
):
f"""Retrieve a distribution of the actual data for {self.resource_name}
identified by its identifier."""
# 1. Get resource from id
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
metadata = self.get_resource(
engine=engine, identifier=identifier, schema=schema, platform=None
)
# 2. get the url field from distribution pointing to the actual data
distribution = metadata.distribution # type:ignore
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
if not distribution:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Distribution not found."
)
elif distribution_idx >= len(distribution):
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Distribution index out of range.",
)

try:
url = distribution[distribution_idx].content_url
encoding_format = distribution[distribution_idx].encoding_format
filename = distribution[distribution_idx].name

async with AsyncClient() as client:
response = await client.get(url)

content = response.content
headers = {
"Content-Disposition": f"attachment; filename={filename}",
"Content-Type": f"{encoding_format}",
josvandervelde marked this conversation as resolved.
Show resolved Hide resolved
}
return Response(content=content, headers=headers)

except Exception as exc:
raise _wrap_as_http_exception(exc)

async def get_resource_data_default(
identifier: str,
schema: Literal[tuple(self._possible_schemas)] = "aiod", # type:ignore
):
f"""Retrieve the first distribution (index 0 as default) of the actual data
for a {self.resource_name} identified by its identifier."""
return await get_resource_data(identifier=identifier, schema=schema, distribution_idx=0)

if default:
return get_resource_data_default

return get_resource_data
3 changes: 3 additions & 0 deletions src/routers/resource_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ def schema_converters(self) -> dict[str, SchemaConverter[RESOURCE, Any]]:
return {}

def create(self, engine: Engine, url_prefix: str) -> APIRouter:
return self._create(engine, url_prefix)

def _create(self, engine: Engine, url_prefix: str) -> APIRouter:
router = APIRouter()
version = f"v{self.version}"
default_kwargs = {
Expand Down
4 changes: 2 additions & 2 deletions src/routers/resource_routers/case_study_router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from database.model.case_study.case_study import CaseStudy
from routers.resource_router import ResourceRouter
from routers.resource_ai_asset_router import ResourceAIAssetRouter


class CaseStudyRouter(ResourceRouter):
class CaseStudyRouter(ResourceAIAssetRouter):
@property
def version(self) -> int:
return 1
Expand Down
4 changes: 2 additions & 2 deletions src/routers/resource_routers/dataset_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
)
from converters.schema_converters.schema_converter import SchemaConverter
from database.model.dataset.dataset import Dataset
from routers.resource_router import ResourceRouter
from routers.resource_ai_asset_router import ResourceAIAssetRouter


class DatasetRouter(ResourceRouter):
class DatasetRouter(ResourceAIAssetRouter):
@property
def version(self) -> int:
return 1
Expand Down
4 changes: 2 additions & 2 deletions src/routers/resource_routers/experiment_router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from database.model.models_and_experiments.experiment import Experiment
from routers.resource_router import ResourceRouter
from routers.resource_ai_asset_router import ResourceAIAssetRouter


class ExperimentRouter(ResourceRouter):
class ExperimentRouter(ResourceAIAssetRouter):
@property
def version(self) -> int:
return 1
Expand Down
4 changes: 2 additions & 2 deletions src/routers/resource_routers/ml_model_router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from database.model.models_and_experiments.ml_model import MLModel
from routers.resource_router import ResourceRouter
from routers.resource_ai_asset_router import ResourceAIAssetRouter


class MLModelRouter(ResourceRouter):
class MLModelRouter(ResourceAIAssetRouter):
@property
def version(self) -> int:
return 1
Expand Down
4 changes: 2 additions & 2 deletions src/routers/resource_routers/publication_router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from database.model.knowledge_asset.publication import Publication
from routers.resource_router import ResourceRouter
from routers.resource_ai_asset_router import ResourceAIAssetRouter


class PublicationRouter(ResourceRouter):
class PublicationRouter(ResourceAIAssetRouter):
@property
def version(self) -> int:
return 1
Expand Down
Loading