Skip to content
This repository was archived by the owner on Aug 20, 2025. It is now read-only.
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ ENV PYTHONPATH "${PYTHONPATH}:/app:/app/dependencies"
# Change user
USER microdata

CMD ["/app/dependencies/bin/gunicorn", "--logger-class", "metadata_service.config.gunicorn.CustomLogger", "metadata_service.app:app", "--workers", "2", "--limit-request-line", "8190"]
CMD ["/app/dependencies/bin/gunicorn", "-k", "uvicorn.workers.UvicornWorker", "--logger-class", "metadata_service.config.gunicorn.CustomLogger", "metadata_service.app:app", "--workers", "2", "--limit-request-line", "8190"]
6 changes: 3 additions & 3 deletions metadata_service/adapter/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_datastore_versions() -> dict:
return json.load(f)


def _get_draft_metadata_all():
def _get_draft_metadata_all() -> dict:
metadata_all_file_path = (
f"{DATASTORE_ROOT_DIR}/datastore/metadata_all__DRAFT.json"
)
Expand All @@ -34,7 +34,7 @@ def _get_draft_metadata_all():


@lru_cache(maxsize=32)
def _get_versioned_metadata_all(version: Version):
def _get_versioned_metadata_all(version: Version) -> dict:
file_version = version.to_3_underscored()
metadata_all_file_path = (
f"{DATASTORE_ROOT_DIR}/datastore/metadata_all__{file_version}.json"
Expand All @@ -43,7 +43,7 @@ def _get_versioned_metadata_all(version: Version):
return json.load(f)


def get_metadata_all(version: Version) -> str:
def get_metadata_all(version: Version) -> dict:
try:
if version.is_draft():
return _get_draft_metadata_all()
Expand Down
91 changes: 32 additions & 59 deletions metadata_service/api/metadata_api.py
Original file line number Diff line number Diff line change
@@ -1,100 +1,73 @@
import logging

from flask import Blueprint, jsonify, request
from fastapi import APIRouter, Depends

from metadata_service.api.request_models import NameParam, MetadataQuery
from metadata_service.domain import metadata
from metadata_service.domain.version import get_version_from_string

logger = logging.getLogger()
metadata_api = Blueprint("metadata_api", __name__)
metadata_router = APIRouter()


@metadata_api.get("/metadata/data-store")
@metadata_router.get("/metadata/data-store")
def get_data_store():
logger.info("GET /metadata/data-store")
return metadata.find_all_datastore_versions()

response = jsonify(metadata.find_all_datastore_versions())
response.headers.set("content-language", "no")
return response


@metadata_api.get("/metadata/data-structures/status")
def get_data_structure_current_status():
validated_query = NameParam(**request.args)
@metadata_router.get("/metadata/data-structures/status")
def get_data_structure_current_status(validated_query: NameParam = Depends()):
logger.info(
f"GET /metadata/data-structures/status with name = {validated_query.names}"
)
response = jsonify(
metadata.find_current_data_structure_status(
validated_query.get_names_as_list()
)
return metadata.find_current_data_structure_status(
validated_query.get_names_as_list()
)
response.headers.set("content-language", "no")
return response


@metadata_api.post("/metadata/data-structures/status")
def get_data_structure_current_status_as_post():
validated_body = NameParam(**request.json)

@metadata_router.post("/metadata/data-structures/status")
def get_data_structure_current_status_as_post(validated_body: NameParam):
logger.info(
f"POST /metadata/data-structures/status with name = {validated_body.names}"
)
response = jsonify(
metadata.find_current_data_structure_status(
validated_body.get_names_as_list()
)
return metadata.find_current_data_structure_status(
validated_body.get_names_as_list()
)
response.headers.set("content-language", "no")
return response


@metadata_api.get("/metadata/data-structures")
def get_data_structures():
validated_query = MetadataQuery(**request.args)
@metadata_router.get("/metadata/data-structures")
def get_data_structures(
validated_query: MetadataQuery = Depends(),
):
validated_query.include_attributes = True
logger.info(f"GET /metadata/data-structures with query: {validated_query}")

response = jsonify(
metadata.find_data_structures(
validated_query.names,
get_version_from_string(validated_query.version),
validated_query.include_attributes,
validated_query.skip_code_lists,
)
return metadata.find_data_structures(
validated_query.names_as_list(),
get_version_from_string(validated_query.version),
validated_query.include_attributes,
validated_query.skip_code_lists,
)
response.headers.set("content-language", "no")
return response


@metadata_api.get("/metadata/all-data-structures")
@metadata_router.get("/metadata/all-data-structures")
def get_all_data_structures_ever():
logger.info("GET /metadata/all-data-structures")
return metadata.find_all_data_structures_ever()

response = jsonify(metadata.find_all_data_structures_ever())
response.headers.set("content-language", "no")
return response


@metadata_api.get("/metadata/all")
def get_all_metadata():
validated_query = MetadataQuery(**request.args)
@metadata_router.get("/metadata/all")
def get_all_metadata(
validated_query: MetadataQuery = Depends(),
):
logger.info(f"GET /metadata/all with version: {validated_query.version}")

response = jsonify(
metadata.find_all_metadata(
get_version_from_string(validated_query.version),
validated_query.skip_code_lists,
)
return metadata.find_all_metadata(
get_version_from_string(validated_query.version),
validated_query.skip_code_lists,
)
response.headers.set("content-language", "no")
return response


@metadata_api.get("/languages")
@metadata_router.get("/languages")
def get_languages():
logger.info("GET /languages")

response = jsonify(metadata.find_languages())
return response
return metadata.find_languages()
12 changes: 6 additions & 6 deletions metadata_service/api/observability.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from flask import Blueprint
from fastapi import APIRouter


observability = Blueprint("observability", __name__)
observability_router = APIRouter()


@observability.get("/health/alive")
def alive():
@observability_router.get("/health/alive")
async def alive():
return "I'm alive!"


@observability.get("/health/ready")
def ready():
@observability_router.get("/health/ready")
async def ready():
return "I'm ready!"
17 changes: 4 additions & 13 deletions metadata_service/api/request_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,11 @@


class MetadataQuery(BaseModel, extra="forbid", validate_assignment=True):
names: List[str] = []
names: str | None = None
version: str
include_attributes: bool = False
skip_code_lists: bool = False

@field_validator("names", mode="before")
@classmethod
def split_str(cls, names):
if isinstance(names, List):
return names[0].split(",")
elif isinstance(names, str):
return names.split(",")
else:
raise RequestValidationException(
"names field must be a list or a string"
)

@field_validator("version", mode="before")
@classmethod
def validate_version(cls, version: str):
Expand All @@ -38,6 +26,9 @@ def validate_version(cls, version: str):
)
return version

def names_as_list(self) -> List[str]:
return [] if self.names is None else self.names.split(",")


class NameParam(BaseModel, extra="forbid"):
names: str
Expand Down
128 changes: 56 additions & 72 deletions metadata_service/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import logging

import msgpack
from flask import Flask, Response, request, jsonify, make_response
from werkzeug.exceptions import NotFound, BadHost

from metadata_service.api.metadata_api import metadata_api
from metadata_service.api.observability import observability
from fastapi.responses import JSONResponse
from fastapi import FastAPI
from starlette.exceptions import HTTPException
from metadata_service.api.metadata_api import metadata_router
from metadata_service.api.observability import observability_router
from metadata_service.config.logging import setup_logging
from metadata_service.config.uvicorn import setup_uvicorn_logging
from metadata_service.exceptions.exceptions import (
DataNotFoundException,
InvalidStorageFormatException,
Expand All @@ -17,99 +17,83 @@

logger = logging.getLogger()

app = Flask(__name__)
app.register_blueprint(observability)
app.register_blueprint(metadata_api)
app = FastAPI()
app.include_router(observability_router)
app.include_router(metadata_router)

setup_logging(app)
setup_uvicorn_logging()


@app.after_request
def after_request(response: Response):
if (
"Accept" in request.headers
and request.headers["Accept"] == "application/x-msgpack"
):
# create a new Response to send the payload only as "data" field
response_msgpack = make_response(msgpack.dumps(response.json))
response_msgpack.headers.set("Content-Type", "application/x-msgpack")
return response_msgpack

@app.middleware("http")
async def add_language_header(request, call_next):
response = await call_next(request)
response.headers.setdefault("Content-Language", "no")
return response


@app.errorhandler(Exception)
def handle_generic_exception(exc):
logger.exception(exc)
return (
jsonify(
{
"code": 202,
"message": f"Error: {str(exc)}",
"service": "metadata-service",
"type": "SYSTEM_ERROR",
}
),
500,
)


@app.errorhandler(NotFound)
def handle_url_invalid(exc):
logger.warning(exc, exc_info=True)
return (
jsonify(
{
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(_req, exc):
if exc.status_code == 404:
return JSONResponse(
content={
"code": 103,
"message": f"Error: {str(exc)}",
"service": "metadata-service",
"type": "PATH_NOT_FOUND",
}
),
400,
},
status_code=400,
)
return JSONResponse(
content={
"code": 202,
"message": f"Error: {str(exc)}",
"service": "metadata-service",
"type": "SYSTEM_ERROR",
},
status_code=500,
)


@app.errorhandler(BadHost)
def handle_bad_host(exc):
logger.warning(exc, exc_info=True)
return (
jsonify(
{
"code": 103,
"message": f"Error: {str(exc)}",
"service": "metadata-service",
"type": "PATH_NOT_FOUND",
}
),
400,
@app.exception_handler(Exception)
def handle_generic_exception(_req, exc):
logger.exception(exc)
return JSONResponse(
content={
"code": 202,
"message": f"Error: {str(exc)}",
"service": "metadata-service",
"type": "SYSTEM_ERROR",
},
status_code=500,
)


@app.errorhandler(DataNotFoundException)
def handle_data_not_found(exc):
@app.exception_handler(DataNotFoundException)
def handle_data_not_found(_req, exc):
logger.warning(exc, exc_info=True)
return jsonify(exc.to_dict()), 404
return JSONResponse(content=exc.to_dict(), status_code=404)


@app.errorhandler(InvalidDraftVersionException)
def handle_invalid_draft(exc):
@app.exception_handler(InvalidDraftVersionException)
def handle_invalid_draft(_req, exc):
logger.warning(exc, exc_info=True)
return str(exc), 404
return JSONResponse(content={"message": str(exc)}, status_code=404)


@app.errorhandler(RequestValidationException)
def handle_invalid_request(exc):
@app.exception_handler(RequestValidationException)
def handle_invalid_request(_req, exc):
logger.warning(exc, exc_info=True)
return jsonify(exc.to_dict()), 400
return JSONResponse(content=exc.to_dict(), status_code=400)


@app.errorhandler(InvalidStorageFormatException)
def handle_invalid_format(exc):
@app.exception_handler(InvalidStorageFormatException)
def handle_invalid_format(_req, exc):
logger.exception(exc)
return jsonify(exc.to_dict()), 500
return JSONResponse(content=exc.to_dict(), status_code=500)


# this is needed to run the application in IDE
if __name__ == "__main__":
app.run(port=8000, host="0.0.0.0")
import uvicorn

uvicorn.run(app, host="0.0.0.0", port=8000)
Loading