-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor existing endpoints and create endpoints
- Loading branch information
1 parent
260286d
commit fc8c9fc
Showing
20 changed files
with
895 additions
and
649 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,3 +30,5 @@ index.json | |
|
||
# Logging files | ||
*.log | ||
|
||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,16 @@ readme = "README.md" | |
description = "" | ||
authors = ["ai-validatie-team <[email protected]>"] | ||
repository = "https://github.com/MinBZK/task-registry" | ||
keywords = ["AI", "Validation", "Instrument", "Task", "Registry"] | ||
keywords = ["AI", "Validation", "Instrument", "Requirement", "Measure", "Task", "Registry"] | ||
license = "EUPL-1.2" | ||
classifiers = [ | ||
"Development Status :: Alpha", | ||
"Framework :: FastAPI", | ||
"Topic :: Software Development :: Libraries :: Python Modules", | ||
"Programming Language :: Python :: 3", | ||
"Topic :: Scientific/Engineering :: Artificial Intelligence", | ||
"Typing :: Typed" | ||
] | ||
packages = [ | ||
{ include = "task_registry" } | ||
] | ||
|
@@ -48,7 +56,7 @@ types-pyyaml = "^6.0.12.20240724" | |
# Ruff settings: https://docs.astral.sh/ruff/configuration/ | ||
[tool.ruff] | ||
line-length = 120 | ||
target-version = "py311" | ||
target-version = "py312" | ||
src = ["task_registry", "tests", "script"] | ||
include = ['script/validate'] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
from fastapi import APIRouter | ||
from task_registry.api.routes import health, instruments, urns | ||
from task_registry.api.routes import health, instruments, measures, requirements | ||
|
||
api_router = APIRouter() | ||
api_router.include_router(health.router, prefix="/health", tags=["health"]) | ||
api_router.include_router(instruments.router, prefix="/instruments", tags=["instruments"]) | ||
api_router.include_router(urns.router, prefix="/urns", tags=["urns"]) | ||
api_router.include_router(measures.router, prefix="/measures", tags=["measures"]) | ||
api_router.include_router(requirements.router, prefix="/requirements", tags=["requirements"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import logging | ||
|
||
from fastapi import APIRouter, HTTPException | ||
from fastapi.responses import JSONResponse | ||
from task_registry.data import Index, TaskType | ||
from task_registry.lifespan import CACHED_REGISTRY | ||
|
||
router = APIRouter() | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@router.get( | ||
"/", | ||
summary="Overview of all the measures in the task registry", | ||
description="This endpoint returns a JSON with all the measures in the task registry.", | ||
responses={200: {"description": "JSON with all the measures."}}, | ||
) | ||
async def get_measures() -> Index: | ||
return CACHED_REGISTRY.get_tasks_index(TaskType.MEASURES) | ||
|
||
|
||
# Optional parameter 'version' is included, but not used. In a new ticket | ||
# versioning of measures should be handled. | ||
@router.get( | ||
"/urn/{urn}", | ||
summary="Get the contents of the specific measure by URN", | ||
description="This endpoint returns a JSON with the contents of a specific measure identified by URN" | ||
" and version.", | ||
responses={ | ||
200: {"description": "JSON with the specific contents of the measure."}, | ||
400: {"description": "The URN does not exist or is not valid."}, | ||
}, | ||
) | ||
async def get_measure(urn: str, version: str = "latest") -> JSONResponse: | ||
try: | ||
content = CACHED_REGISTRY.get_task(urn, TaskType.MEASURES) | ||
return JSONResponse(content=content) | ||
except KeyError as err: | ||
raise HTTPException(status_code=400, detail=f"invalid urn: {urn}") from err |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import logging | ||
|
||
from fastapi import APIRouter, HTTPException | ||
from fastapi.responses import JSONResponse | ||
from task_registry.data import Index, TaskType | ||
from task_registry.lifespan import CACHED_REGISTRY | ||
|
||
router = APIRouter() | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@router.get( | ||
"/", | ||
summary="Overview of all the requirements in the task registry.", | ||
description="This endpoint returns a JSON with all the requirements in the task registry.", | ||
responses={200: {"description": "JSON with all the requirements."}}, | ||
) | ||
async def get_requirements() -> Index: | ||
return CACHED_REGISTRY.get_tasks_index(TaskType.REQUIREMENTS) | ||
|
||
|
||
# Optional parameter 'version' is included, but not used. In a new ticket | ||
# versioning of requirements should be handled. | ||
@router.get( | ||
"/urn/{urn}", | ||
summary="Get the contents of the specific instrument which has given URN.", | ||
description="This endpoint returns a JSON with the contents of a specific instrument identified by URN" | ||
" and version.", | ||
responses={ | ||
200: {"description": "JSON with the specific contents of the instrument."}, | ||
400: {"description": "The URN does not exist or is not valid."}, | ||
}, | ||
) | ||
async def get_requirement(urn: str, version: str = "latest") -> JSONResponse: | ||
try: | ||
content = CACHED_REGISTRY.get_task(urn, TaskType.REQUIREMENTS) | ||
return JSONResponse(content=content) | ||
except KeyError as err: | ||
raise HTTPException(status_code=400, detail=f"invalid urn: {urn}") from err |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,115 @@ | ||
import logging | ||
import os | ||
from pathlib import Path | ||
from enum import StrEnum | ||
from typing import Any | ||
|
||
import yaml | ||
from pydantic import BaseModel, Field | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def get_file_size(file_path: str) -> int: | ||
return os.path.getsize(file_path) # pragma: no cover | ||
class TaskType(StrEnum): | ||
INSTRUMENTS = "instruments" | ||
REQUIREMENTS = "requirements" | ||
MEASURES = "measures" | ||
|
||
|
||
def create_urn_mappper(entries: list[dict[str, Any]]) -> dict[str, Any]: | ||
urn_mapper: dict[str, Any] = {} | ||
for instrument in entries: | ||
path = Path(instrument["path"]) | ||
try: | ||
with open(str(path)) as f: | ||
urn_mapper[instrument["urn"]] = yaml.safe_load(f) | ||
except FileNotFoundError: | ||
logger.exception(f"Instrument file with path {path} not found.") # pragma: no cover | ||
class Link(BaseModel): | ||
self: str | ||
|
||
except yaml.YAMLError: | ||
logger.exception(f"Instrument file with path {path} could not be parsed.") # pragma: no cover | ||
|
||
return urn_mapper | ||
class FileInfo(BaseModel): | ||
type: str | ||
size: int | ||
name: str | ||
path: str | ||
urn: str | ||
download_url: str | ||
links: Link | ||
|
||
|
||
class Index(BaseModel): | ||
type: str = Field(examples=["dir"]) | ||
size: int = Field(examples=[0]) | ||
name: str = Field(examples=["task_collection_name"]) | ||
path: str = Field(examples=["task_collection_path"]) | ||
download_url: str = Field(examples=["https://task-registry.apps.digilab.network/task_collection"]) | ||
links: Link = Field(examples=[{"self": "https://task-registry.apps.digilab.network"}]) | ||
entries: list[FileInfo] = Field( | ||
examples=[ | ||
{ | ||
"type": "file", | ||
"size": 1024, | ||
"name": "task_name.yaml", | ||
"path": "task_collection_path/task_name.yaml", | ||
"urn": "urn:nl:aivt:tr:xx:xx", | ||
"download_url": "https://task-registry.apps.digilab.network/task_collection/urn/urn:nl:aivt:tr:aiia:1.0", | ||
"links": { | ||
"self": "https://task-registry.apps.digilab.network/task_collection/urn/urn:nl:aivt:tr:aiia:1.0" | ||
}, | ||
} | ||
] | ||
) | ||
|
||
|
||
class CachedRegistry: | ||
def __init__(self) -> None: | ||
self.index_cache: dict[TaskType, Index] = {} | ||
self.tasks_cache: dict[tuple[str, TaskType], Any] = {} | ||
|
||
def add_tasks(self, tasks: TaskType) -> None: | ||
index = generate_index(tasks) | ||
self.index_cache[tasks] = index | ||
|
||
for task in index.entries: | ||
try: | ||
with open(task.path) as f: | ||
self.tasks_cache[(task.urn, tasks)] = yaml.safe_load(f) | ||
except FileNotFoundError: | ||
logger.exception(f"Task file with path {task.path} not found.") # pragma: no cover | ||
except yaml.YAMLError: | ||
logger.exception(f"Task file with path {task.path} could not be parsed.") # pragma: no cover | ||
|
||
def get_tasks_index(self, tasks: TaskType) -> Index: | ||
return self.index_cache[tasks] | ||
|
||
def get_task(self, urn: str, tasks: TaskType) -> dict[str, Any]: | ||
return self.tasks_cache[(urn, tasks)] | ||
|
||
|
||
def generate_index( | ||
tasks: TaskType, | ||
base_url: str = "https://task-registry.apps.digilab.network", | ||
directory: str = "instruments", | ||
) -> dict[str, Any]: | ||
index: dict[str, Any] = { | ||
"type": "dir", | ||
"size": 0, | ||
"name": directory, | ||
"path": directory, | ||
"download_url": f"{base_url}/instruments", | ||
"_links": { | ||
"self": f"{base_url}/instruments", | ||
}, | ||
"entries": [], | ||
} | ||
|
||
for root, _, files in os.walk(directory): | ||
) -> Index: | ||
tasks_url = f"{base_url}/{tasks}" | ||
entries: list[FileInfo] = [] | ||
|
||
for root, _, files in os.walk(tasks): | ||
for file in files: | ||
if file.endswith(".yaml"): | ||
file_path = os.path.join(root, file) | ||
relative_path = file_path.replace("\\", "/") | ||
with open(file_path) as f: | ||
instrument = yaml.safe_load(f) | ||
file_info = { | ||
"type": "file", | ||
"size": get_file_size(file_path), | ||
"name": file, | ||
"path": relative_path, | ||
"urn": instrument["urn"], | ||
"download_url": f"{base_url}/urns/?urn={instrument['urn']}", | ||
"_links": { | ||
"self": f"{base_url}/urns/?urn={instrument['urn']}", | ||
}, | ||
} | ||
index["entries"].append(file_info) | ||
|
||
return index | ||
task = yaml.safe_load(f) | ||
task_url = f"{tasks_url}/urn/{task['urn']}" | ||
file_info = FileInfo( | ||
type="file", | ||
size=os.path.getsize(file_path), | ||
name=file, | ||
path=relative_path, | ||
urn=task["urn"], | ||
download_url=task_url, | ||
links=Link(self=task_url), | ||
) | ||
entries.append(file_info) | ||
|
||
return Index( | ||
type="dir", | ||
size=0, | ||
name=tasks.value, | ||
path=tasks.value, | ||
download_url=tasks_url, | ||
links=Link(self=tasks_url), | ||
entries=entries, | ||
) |
Oops, something went wrong.