Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
add model metadata route
  • Loading branch information
atharvakale343 committed Oct 31, 2024
commit 9b6398c1ab0f8a03711eb25a94e221374099caf5
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "flask_ml"
version = "0.2.4"
version = "0.2.5"
authors = [
{ name="Prasanna Lakkur Subramanyam", email="psubramanyam@umass.edu" },
{ name="Atharva Kale", email="aukale@umass.edu" },
Expand Down
11 changes: 11 additions & 0 deletions src/flask_ml/flask_ml_server/MLServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ResponseBody,
SchemaAPIRoute,
TaskSchema,
AppMetadata
)
from flask_ml.flask_ml_server.utils import (
ensure_ml_func_hinting_and_task_schemas_are_valid,
Expand Down Expand Up @@ -60,6 +61,7 @@ def __init__(self, name):
"""
self.app = Flask(name, static_folder=None)
self.endpoints: List[EndpointDetailsNoSchema] = []
self._app_metadata: Optional[AppMetadata] = None

@self.app.route("/api/routes", methods=["GET"])
def list_routes():
Expand All @@ -86,6 +88,15 @@ def list_routes():
for endpoint in self.endpoints
]
return jsonify(APIRoutes(root=routes).model_dump(mode="json"))

@self.app.route("/api/app_metadata", methods=["GET"])
def get_app_metadata():
if self._app_metadata is None:
return jsonify({"error": "App metadata not set"})
return jsonify(self._app_metadata.model_dump(mode="json"))

def add_app_metadata(self, app_metadata: AppMetadata):
self._app_metadata = app_metadata

def route(
self,
Expand Down
8 changes: 8 additions & 0 deletions src/flask_ml/flask_ml_server/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from pathlib import Path
from .MLServer import MLServer

__all__ = ["MLServer"] # for flake8 unused import error

def load_file_as_string(file_path: str) -> str:
fp = Path(file_path)
if not fp.is_file():
raise FileNotFoundError(f"File {file_path} not found")
with open(file_path, "r") as f:
return f.read()
7 changes: 3 additions & 4 deletions src/flask_ml/flask_ml_server/models.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
# generated by datamodel-codegen:
# filename: openapi.yaml
# timestamp: 2024-10-24T01:31:17+00:00
# timestamp: 2024-10-31T02:34:36+00:00

from __future__ import annotations

from datetime import datetime
from enum import Enum
from typing import Annotated, Any, Dict, List, Optional, Union

from pydantic import BaseModel, ConfigDict, Field, RootModel


class InfoPage(BaseModel):
class AppMetadata(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
)
info: Annotated[str, Field(description='Markdown content to render on the info page')]
author: str
version: str
last_updated: Annotated[datetime, Field(alias='lastUpdated')]
name: Annotated[str, Field(examples=['Face Match App'])]


class SchemaAPIRoute(BaseModel):
Expand Down
17 changes: 9 additions & 8 deletions src/flask_ml/flask_ml_server/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@

openapi: 3.0.0
info:
title: FlaskML
version: 1.0.0
description: API for processing machine learning inputs and returning results.
paths:
/info:
/api/app_metadata:
get:
summary: Get Info Page
summary: Get App Metadata
responses:
'200':
description: Info page rendered in markdown
description: App metadata
content:
application/json:
schema:
$ref: '#/components/schemas/InfoPage'
$ref: '#/components/schemas/AppMetadata'

/api/routes:
get:
Expand Down Expand Up @@ -86,9 +87,9 @@ paths:
components:
schemas:
# Info Page models
InfoPage:
AppMetadata:
type: object
required: [info, author, version, lastUpdated]
required: [info, name, author, version]
properties:
info:
type: string
Expand All @@ -97,9 +98,9 @@ components:
type: string
version:
type: string
lastUpdated:
name:
type: string
format: date-time
example: "Face Match App"
example:
info: "# Welcome to the Face Match App\n\nThis app will help you to match faces in your images..."

Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def process_directories(inputs: List[DirectoryInput], parameters):
return results


@pytest.fixture(scope="session", autouse=True)
@pytest.fixture(autouse=True)
def server():
server = MLServer(__name__)

Expand Down Expand Up @@ -187,6 +187,6 @@ def server_process_text_input_with_text_area_schema(
return server


@pytest.fixture(scope="session", autouse=True)
@pytest.fixture(autouse=True)
def app(server: MLServer):
return server.app.test_client()
47 changes: 47 additions & 0 deletions tests/test_app_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path
from re import T
from flask_ml.flask_ml_server import MLServer, load_file_as_string
from flask.testing import FlaskClient
import pytest

from flask_ml.flask_ml_server.models import AppMetadata

TEST_MARKDOWN_FILE_PATH = "test_markdown_file_path.md"
TEST_MARKDOWN_FILE_CONTENT = "# This is a test markdown file"


@pytest.fixture
def test_markdown_file_path(tmp_path: Path) -> str:
file_path = tmp_path / TEST_MARKDOWN_FILE_PATH
file_path.write_text(TEST_MARKDOWN_FILE_CONTENT)
return str(file_path)


def test_app_metadata_provided(server: MLServer, app: FlaskClient, test_markdown_file_path: str):
server.add_app_metadata(
app_metadata=AppMetadata(
info=load_file_as_string(test_markdown_file_path),
author="Test Author",
version="1.0.0",
name="Test App",
)
)

response = app.get("/api/app_metadata")
assert response.status_code == 200
assert response.json == {
"info": TEST_MARKDOWN_FILE_CONTENT,
"author": "Test Author",
"version": "1.0.0",
"name": "Test App",
}


def test_app_metadata_not_provided(app: FlaskClient):
response = app.get("/api/app_metadata")
assert response.status_code == 200
assert response.json == {"error": "App metadata not set"}

def test_invalid_file_path():
with pytest.raises(FileNotFoundError):
load_file_as_string("invalid_file_path.md")