-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/SK-1405 | Add analytics routes and store (#811)
- Loading branch information
1 parent
1869ede
commit d03b3c0
Showing
5 changed files
with
126 additions
and
2 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
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,39 @@ | ||
from flask import Blueprint, jsonify, request | ||
|
||
from fedn.common.log_config import logger | ||
from fedn.network.api.auth import jwt_auth_required | ||
from fedn.network.api.shared import analytic_store | ||
from fedn.network.api.v1.shared import api_version, get_typed_list_headers | ||
|
||
bp = Blueprint("analytic", __name__, url_prefix=f"/api/{api_version}/analytics") | ||
|
||
|
||
@bp.route("/", methods=["GET"]) | ||
@jwt_auth_required(role="admin") | ||
def get_analytics(): | ||
try: | ||
limit, skip, sort_key, sort_order = get_typed_list_headers(request.headers) | ||
kwargs = request.args.to_dict() | ||
|
||
response = analytic_store.list(limit, skip, sort_key, sort_order, **kwargs) | ||
|
||
return jsonify(response), 200 | ||
except Exception as e: | ||
logger.error(f"An unexpected error occurred: {e}") | ||
return jsonify({"message": "An unexpected error occurred"}), 500 | ||
|
||
|
||
@bp.route("/", methods=["POST"]) | ||
@jwt_auth_required(role="admin") | ||
def add_analytics(): | ||
try: | ||
data = request.json if request.headers["Content-Type"] == "application/json" else request.form.to_dict() | ||
|
||
successful, result = analytic_store.add(data) | ||
response = result | ||
status_code: int = 201 if successful else 400 | ||
|
||
return jsonify(response), status_code | ||
except Exception as e: | ||
logger.error(f"An unexpected error occurred: {e}") | ||
return jsonify({"message": "An unexpected error occurred"}), 500 |
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,64 @@ | ||
from datetime import datetime | ||
from typing import Any, Dict, List, Tuple | ||
|
||
import pymongo | ||
from pymongo.database import Database | ||
|
||
from fedn.network.storage.statestore.stores.store import MongoDBStore, Store | ||
|
||
|
||
class Analytic: | ||
def __init__(self, id: str, client_id: str, type: str, execution_duration: int, model_id: str, committed_at: datetime): | ||
self.id = id | ||
self.client_id = client_id | ||
self.type = type | ||
self.execution_duration = execution_duration | ||
self.model_id = model_id | ||
self.committed_at = committed_at | ||
|
||
|
||
class AnalyticStore(Store[Analytic]): | ||
pass | ||
|
||
|
||
def _validate_analytic(analytic: dict) -> Tuple[bool, str]: | ||
if "client_id" not in analytic: | ||
return False, "client_id is required" | ||
if "type" not in analytic or analytic["type"] not in ["training", "inference"]: | ||
return False, "type must be either 'training' or 'inference'" | ||
return analytic, "" | ||
|
||
|
||
def _complete_analytic(analytic: dict) -> dict: | ||
if "committed_at" not in analytic: | ||
analytic["committed_at"] = datetime.now() | ||
|
||
|
||
class MongoDBAnalyticStore(AnalyticStore, MongoDBStore[Analytic]): | ||
def __init__(self, database: Database, collection: str): | ||
super().__init__(database, collection) | ||
self.database[self.collection].create_index([("client_id", pymongo.DESCENDING)]) | ||
|
||
def get(self, id: str) -> Analytic: | ||
return super().get(id) | ||
|
||
def update(self, id: str, item: Analytic) -> Tuple[bool, Any]: | ||
pass | ||
|
||
def add(self, item: Analytic) -> Tuple[bool, Any]: | ||
valid, msg = _validate_analytic(item) | ||
if not valid: | ||
return False, msg | ||
|
||
_complete_analytic(item) | ||
|
||
return super().add(item) | ||
|
||
def delete(self, id: str) -> bool: | ||
pass | ||
|
||
def list(self, limit: int, skip: int, sort_key: str, sort_order=pymongo.DESCENDING, **kwargs) -> Dict[int, List[Analytic]]: | ||
return super().list(limit, skip, sort_key or "committed_at", sort_order, **kwargs) | ||
|
||
def count(self, **kwargs) -> int: | ||
return super().count(**kwargs) |