Skip to content

Commit

Permalink
Add User Processing Stats, User List, and OCR-D Processor Discovery E…
Browse files Browse the repository at this point in the history
…ndpoints (#19)

* Add /admin/users endpoint with PYUserInfo model

* Fix type hint for Python 3.8 compatibility in db_get_all_user_accounts

* Add GET /admin/processing_stats/{user_id} endpoint

* Add GET /discovery/processors endpoint

* Fix: Use List from typing for Python 3.8 compatibility

* Fix: Import ocrd json file from constants.

* Fix: Import error

* Add GET /discovery/processor/{processor_name} endpoint

* Refactor db_get_all_user_accounts to return an empty list and remove unnecessary try-except block in admin_panel.py.

* Update src/server/operandi_server/routers/admin_panel.py

Co-authored-by: Mehmed Mustafa <mehmed.n.mustafa@gmail.com>

* Update src/server/operandi_server/routers/discovery.py

Co-authored-by: Mehmed Mustafa <mehmed.n.mustafa@gmail.com>

* Update src/server/operandi_server/routers/discovery.py

Co-authored-by: Mehmed Mustafa <mehmed.n.mustafa@gmail.com>

* Update: log the exception with logger

---------

Co-authored-by: Mehmed Mustafa <mehmed.n.mustafa@gmail.com>
  • Loading branch information
Faizan-hub and MehmedGIT authored Dec 2, 2024
1 parent 602636e commit 56da368
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/server/operandi_server/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__all__ = [
"PYDiscovery",
"PYUserAction",
"PYUserInfo",
"Resource",
"SbatchArguments",
"WorkflowArguments",
Expand All @@ -11,6 +12,7 @@

from .base import Resource, SbatchArguments, WorkflowArguments
from .discovery import PYDiscovery
from .user import PYUserAction
from .user import PYUserAction, PYUserInfo
from .workflow import WorkflowRsrc, WorkflowJobRsrc
from .workspace import WorkspaceRsrc

23 changes: 23 additions & 0 deletions src/server/operandi_server/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,26 @@ def from_db_user_account(action: str, db_user_account: DBUserAccount):
details=db_user_account.details,
action=action
)


class PYUserInfo(BaseModel):
institution_id: str = Field(..., description="Institution id of the user")
user_id: str = Field(..., description="Unique id of the user")
email: str = Field(..., description="Email linked to this User")
account_type: AccountType = Field(AccountType.UNSET, description="The type of this user")
approved_user: bool = Field(False, description="Whether the account was admin approved and fully functional")
details: str = Field(..., description="More details about the account")

class Config:
allow_population_by_field_name = True

@staticmethod
def from_db_user_account(db_user_account: DBUserAccount):
return PYUserInfo(
institution_id=db_user_account.institution_id,
user_id=db_user_account.user_id,
email=db_user_account.email,
account_type=db_user_account.account_type,
approved_user=db_user_account.approved_user,
details=db_user_account.details
)
46 changes: 46 additions & 0 deletions src/server/operandi_server/routers/admin_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

from operandi_server.models import PYUserInfo
from operandi_utils.constants import AccountType, ServerApiTag
from operandi_utils.database import db_get_all_user_accounts, db_get_processing_stats
from operandi_utils.utils import send_bag_to_ola_hd
from .user import RouterUser
from .workspace_utils import create_workspace_bag, get_db_workspace_with_handling, validate_bag_with_handling
Expand All @@ -18,6 +20,16 @@ def __init__(self):
endpoint=self.push_to_ola_hd, methods=["POST"], status_code=status.HTTP_201_CREATED,
summary="Push a workspace to Ola-HD service"
)
self.router.add_api_route(
path="/admin/users",
endpoint=self.get_users, methods=["GET"], status_code=status.HTTP_200_OK,
summary="Get all registered users"
)
self.router.add_api_route(
path="/admin/processing_stats/{user_id}",
endpoint=self.get_processing_stats_for_user, methods=["GET"], status_code=status.HTTP_200_OK,
summary="Get processing stats for a specific user by user_id"
)

async def push_to_ola_hd(self, workspace_id: str, auth: HTTPBasicCredentials = Depends(HTTPBasic())):
py_user_action = await self.user_authenticator.user_login(auth)
Expand Down Expand Up @@ -46,3 +58,37 @@ async def push_to_ola_hd(self, workspace_id: str, auth: HTTPBasicCredentials = D
"pid": pid
}
return response_message

async def get_users(self, auth: HTTPBasicCredentials = Depends(HTTPBasic())):
# Authenticate the user and ensure they have admin privileges
py_user_action = await self.user_authenticator.user_login(auth)
if py_user_action.account_type != AccountType.ADMIN:
message = "Admin privileges required for the endpoint"
self.logger.error(message)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=message)

users = await db_get_all_user_accounts()
return [PYUserInfo.from_db_user_account(user) for user in users]

async def get_processing_stats_for_user(self, user_id: str, auth: HTTPBasicCredentials = Depends(HTTPBasic())):
# Authenticate the admin user
py_user_action = await self.user_authenticator.user_login(auth)
if py_user_action.account_type != AccountType.ADMIN:
message = f"Admin privileges required for the endpoint"
self.logger.error(f"{message}")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=message)

# Retrieve the processing stats for the specified user
try:
db_processing_stats = await db_get_processing_stats(user_id)
if not db_processing_stats:
message = f"Processing stats not found for the user_id: {user_id}"
self.logger.error(message)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
except Exception as error:
message = f"Failed to fetch processing stats for user_id: {user_id}, error: {error}"
self.logger.error(message)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=message)

# Return the processing stats in the response model
return db_processing_stats
62 changes: 61 additions & 1 deletion src/server/operandi_server/routers/discovery.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""
module for implementing the discovery section of the api
"""
from json import JSONDecodeError
from logging import getLogger
from os import cpu_count
from psutil import virtual_memory
from fastapi import APIRouter, Depends, status
from typing import List, Dict
from fastapi import APIRouter, Depends, status, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials

from operandi_utils.constants import ServerApiTag
from operandi_utils.oton.constants import OCRD_ALL_JSON
from operandi_server.models import PYDiscovery
from .user import RouterUser

Expand All @@ -24,6 +27,16 @@ def __init__(self):
summary="List Operandi Server properties",
response_model=PYDiscovery, response_model_exclude_unset=True, response_model_exclude_none=True
)
self.router.add_api_route(
path="/discovery/processors",
endpoint=self.get_processor_names, methods=["GET"], status_code=status.HTTP_200_OK,
summary="List OCR-D processor names"
)
self.router.add_api_route(
path="/discovery/processor/{processor_name}",
endpoint=self.get_processor_info, methods=["GET"], status_code=status.HTTP_200_OK,
summary="Get information about a specific OCR-D processor"
)

async def discovery(self, auth: HTTPBasicCredentials = Depends(HTTPBasic())) -> PYDiscovery:
await self.user_authenticator.user_login(auth)
Expand All @@ -37,3 +50,50 @@ async def discovery(self, auth: HTTPBasicCredentials = Depends(HTTPBasic())) ->
has_docker=False
)
return response

async def get_processor_names(self, auth: HTTPBasicCredentials = Depends(HTTPBasic())) -> List[str]:
# Authenticate the user
await self.user_authenticator.user_login(auth)

try:
# Load JSON and extract processor names
processor_names = list(OCRD_ALL_JSON.keys())
return processor_names


except JSONDecodeError as e:
# Raise a 500 error if the JSON is invalid or cannot be parsed
message = f"Error decoding processor data file: {str(e)}"
# Log the detailed message
self.logger.error(message)
# Raise the HTTPException with the same message
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=message
)

except Exception as e:
# Raise a generic 500 error for any other exceptions
self.logger.error(f"Unexpected error while loading processors: {e}")
raise HTTPException(
status_code=500,
detail="An unexpected error occurred while loading processor names."
)

async def get_processor_info(self, processor_name: str, auth: HTTPBasicCredentials = Depends(HTTPBasic())) -> Dict:
await self.user_authenticator.user_login(auth)

try:
# Check if the processor name exists in the JSON
if processor_name not in OCRD_ALL_JSON:
raise HTTPException(status_code=404, detail=f"Processor '{processor_name}' not found.")

# Retrieve processor information as a dictionary
processor_info = OCRD_ALL_JSON[processor_name]
return processor_info

except JSONDecodeError:
raise HTTPException(status_code=500, detail="Error decoding processor data file.")
except Exception as e:
self.logger.error(f"Error retrieving processor info for {processor_name}: {e}")
raise HTTPException(status_code=500, detail="An unexpected error occurred.")
4 changes: 4 additions & 0 deletions src/utils/operandi_utils/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"db_create_workspace",
"db_get_hpc_slurm_job",
"db_get_processing_stats",
"db_get_all_user_accounts",
"db_get_user_account",
"db_get_user_account_with_email",
"db_get_workflow",
Expand All @@ -33,6 +34,7 @@
"sync_db_create_workspace",
"sync_db_get_hpc_slurm_job",
"sync_db_get_processing_stats",
"sync_db_get_all_user_accounts",
"sync_db_get_user_account",
"sync_db_get_user_account_with_email",
"sync_db_get_workflow",
Expand All @@ -59,9 +61,11 @@
)
from .db_user_account import (
db_create_user_account,
db_get_all_user_accounts,
db_get_user_account,
db_get_user_account_with_email,
db_update_user_account,
sync_db_get_all_user_accounts,
sync_db_create_user_account,
sync_db_get_user_account,
sync_db_get_user_account_with_email,
Expand Down
11 changes: 11 additions & 0 deletions src/utils/operandi_utils/database/db_user_account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime
from operandi_utils import call_sync, generate_id
from typing import List
from ..constants import AccountType
from .models import DBUserAccount

Expand Down Expand Up @@ -32,6 +33,15 @@ async def sync_db_create_user_account(
institution_id, email, encrypted_pass, salt, account_type, approved_user, details)


async def db_get_all_user_accounts() -> List[DBUserAccount]:
all_user_accounts = await DBUserAccount.find().to_list()
return all_user_accounts

@call_sync
async def sync_db_get_all_user_accounts() -> List[DBUserAccount]:
return await db_get_all_user_accounts()


async def db_get_user_account(user_id: str) -> DBUserAccount:
db_user_account = await DBUserAccount.find_one(DBUserAccount.user_id == user_id)
if not db_user_account:
Expand Down Expand Up @@ -87,3 +97,4 @@ async def db_update_user_account(user_id: str, **kwargs) -> DBUserAccount:
@call_sync
async def sync_db_update_user_account(user_id: str, **kwargs) -> DBUserAccount:
return await db_update_user_account(user_id=user_id, **kwargs)

0 comments on commit 56da368

Please sign in to comment.