Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d7b8ce8
Make sure function job was successful before returning cache
wvangeit Jun 2, 2025
cdc111b
Merge branch 'master' into function_cache_check_success
wvangeit Jun 2, 2025
d187c42
Merge branch 'master' into function_cache_check_success
wvangeit Jun 2, 2025
a72507a
Fix return type error
wvangeit Jun 2, 2025
82460f7
Merge branch 'function_cache_check_success' of github.com:wvangeit/os…
wvangeit Jun 2, 2025
e8da50c
Fix mypy bug
wvangeit Jun 2, 2025
9300f6e
Merge branch 'master' into function_cache_check_success
wvangeit Jun 2, 2025
45cfd1f
Merge branch 'master' into function_cache_check_success
wvangeit Jun 2, 2025
d26df1e
Merge branch 'master' into function_cache_check_success
wvangeit Jun 3, 2025
6314ffe
Return the created_at data of registered function api objects
wvangeit Jun 3, 2025
fdadd8a
Merge branch 'master' into add_registered_at_to_functions
wvangeit Jun 3, 2025
5ebc54c
Update openapi specs
wvangeit Jun 3, 2025
27494dd
Add error handlers for functions api
wvangeit Jun 3, 2025
8df445f
Merge branch 'master' into handle_function_errors_api
wvangeit Jun 4, 2025
f18c72b
Fix import typecheck
wvangeit Jun 4, 2025
6ff8037
Merge branch 'master' into handle_function_errors_api
wvangeit Jun 4, 2025
12770c5
Fix mypy typecheck
wvangeit Jun 4, 2025
6831d81
Import fastapi for status
wvangeit Jun 4, 2025
520f55d
Remove fastapi import from function models
wvangeit Jun 4, 2025
529f40a
Merge branch 'master' into handle_function_errors_api
wvangeit Jun 4, 2025
c2bbc45
Merge branch 'handle_function_errors_api' of github.com:wvangeit/ospa…
wvangeit Jun 4, 2025
e7e3db4
Merge branch 'master' into handle_function_errors_api
wvangeit Jun 5, 2025
88d14b5
Merge branch 'master' into handle_function_errors_api
wvangeit Jun 5, 2025
240c0a2
Merge branch 'master' into handle_function_errors_api
mergify[bot] Jun 5, 2025
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
19 changes: 18 additions & 1 deletion packages/models-library/src/models_library/functions_errors.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,102 @@
from common_library.errors_classes import OsparcErrorMixin
from servicelib.aiohttp import status


class FunctionBaseError(OsparcErrorMixin, Exception):
pass
status_code: int


class FunctionJobReadAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} read access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionIDNotFoundError(FunctionBaseError):
msg_template: str = "Function {function_id} not found"
status_code: int = status.HTTP_404_NOT_FOUND


class FunctionJobIDNotFoundError(FunctionBaseError):
msg_template: str = "Function job {function_job_id} not found"
status_code: int = status.HTTP_404_NOT_FOUND


class FunctionInputsValidationError(FunctionBaseError):
msg_template: str = "Function inputs validation failed: {error}"
status_code: int = status.HTTP_422_UNPROCESSABLE_ENTITY


class FunctionReadAccessDeniedError(FunctionBaseError):
msg_template: str = "Function {function_id} read access denied for user {user_id}"
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionJobCollectionIDNotFoundError(FunctionBaseError):
msg_template: str = "Function job collection {function_job_collection_id} not found"
status_code: int = status.HTTP_404_NOT_FOUND


class UnsupportedFunctionClassError(FunctionBaseError):
msg_template: str = "Function class {function_class} is not supported"
status_code: int = status.HTTP_400_BAD_REQUEST


class UnsupportedFunctionJobClassError(FunctionBaseError):
msg_template: str = "Function job class {function_job_class} is not supported"
status_code: int = status.HTTP_400_BAD_REQUEST


class UnsupportedFunctionFunctionJobClassCombinationError(FunctionBaseError):
msg_template: str = (
"Function class {function_class} and function job class {function_job_class} combination is not supported"
)
status_code: int = status.HTTP_400_BAD_REQUEST


class FunctionJobCollectionReadAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} read access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionWriteAccessDeniedError(FunctionBaseError):
msg_template: str = "Function {function_id} write access denied for user {user_id}"
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionJobWriteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} write access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionJobCollectionWriteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} write access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function {function_id} execute access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionJobExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job {function_job_id} execute access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN


class FunctionJobCollectionExecuteAccessDeniedError(FunctionBaseError):
msg_template: str = (
"Function job collection {function_job_collection_id} execute access denied for user {user_id}"
)
status_code: int = status.HTTP_403_FORBIDDEN
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from httpx import HTTPError as HttpxException
from models_library.functions_errors import FunctionBaseError
from starlette import status
from starlette.exceptions import HTTPException

Expand All @@ -9,6 +10,7 @@
from ..custom_errors import CustomBaseError
from ..log_streaming_errors import LogStreamingBaseError
from ._custom_errors import custom_error_handler
from ._handler_function_errors import function_error_handler
from ._handlers_backend_errors import backend_error_handler
from ._handlers_factory import make_handler_for_exception
from ._http_exceptions import http_exception_handler
Expand All @@ -24,6 +26,7 @@ def setup(app: FastAPI, *, is_debug: bool = False):
app.add_exception_handler(LogStreamingBaseError, log_handling_error_handler)
app.add_exception_handler(CustomBaseError, custom_error_handler)
app.add_exception_handler(BaseBackEndError, backend_error_handler)
app.add_exception_handler(FunctionBaseError, function_error_handler)

# SEE https://docs.python.org/3/library/exceptions.html#exception-hierarchy
app.add_exception_handler(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import Request
from models_library.functions_errors import FunctionBaseError

from ._utils import create_error_json_response


async def function_error_handler(request: Request, exc: Exception):
assert request # nosec
assert isinstance(exc, FunctionBaseError)

return create_error_json_response(f"{exc}", status_code=exc.status_code)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from uuid import uuid4

import httpx
import pytest
from httpx import AsyncClient
from models_library.api_schemas_webserver.functions import (
FunctionJobCollection,
Expand All @@ -17,7 +16,10 @@
RegisteredProjectFunction,
RegisteredProjectFunctionJob,
)
from models_library.functions_errors import FunctionIDNotFoundError
from models_library.functions_errors import (
FunctionIDNotFoundError,
FunctionReadAccessDeniedError,
)
from models_library.rest_pagination import PageMetaInfoLimitOffset
from servicelib.aiohttp import status
from simcore_service_api_server._meta import API_VTAG
Expand Down Expand Up @@ -92,8 +94,35 @@ async def test_get_function_not_found(
None,
FunctionIDNotFoundError(function_id=non_existent_function_id),
)
with pytest.raises(FunctionIDNotFoundError):
await client.get(f"{API_VTAG}/functions/{non_existent_function_id}", auth=auth)
response = await client.get(
f"{API_VTAG}/functions/{non_existent_function_id}", auth=auth
)
assert response.status_code == status.HTTP_404_NOT_FOUND


async def test_get_function_read_access_denied(
client: AsyncClient,
mock_handler_in_functions_rpc_interface: Callable[
[str, Any, Exception | None], None
],
mock_registered_function: RegisteredProjectFunction,
auth: httpx.BasicAuth,
) -> None:
unauthorized_user_id = "unauthorized user"
mock_handler_in_functions_rpc_interface(
"get_function",
None,
FunctionReadAccessDeniedError(
function_id=mock_registered_function.uid, user_id=unauthorized_user_id
),
)
response = await client.get(
f"{API_VTAG}/functions/{mock_registered_function.uid}", auth=auth
)
assert response.status_code == status.HTTP_403_FORBIDDEN
assert response.json()["errors"][0] == (
f"Function {mock_registered_function.uid} read access denied for user {unauthorized_user_id}"
)


async def test_list_functions(
Expand Down
Loading