Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3670,6 +3670,33 @@ paths:
type: boolean
default: false
title: Include Extras
- name: search
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Search
- name: filters
in: query
required: false
schema:
anyOf:
- type: string
contentMediaType: application/json
contentSchema: {}
- type: 'null'
title: Filters
- name: order_by
in: query
required: false
schema:
type: string
contentMediaType: application/json
contentSchema: {}
default: '{"field":"modified","direction":"desc"}'
title: Order By
- name: limit
in: query
required: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from models_library.groups import GroupID
from models_library.products import ProductName
from models_library.rest_ordering import OrderBy
from models_library.rest_pagination import Page
from models_library.rest_pagination_utils import paginate_data
from models_library.users import UserID
Expand All @@ -43,6 +44,7 @@
from .._services_metadata.proxy import ServiceMetadata
from ._functions_rest_exceptions import handle_rest_requests_exceptions
from ._functions_rest_schemas import (
FunctionFilters,
FunctionGetQueryParams,
FunctionGroupPathParams,
FunctionPathParams,
Expand Down Expand Up @@ -172,13 +174,21 @@ async def list_functions(request: web.Request) -> web.Response:
FunctionsListQueryParams, request
)

if not query_params.filters:
query_params.filters = FunctionFilters()

assert query_params.filters # nosec

req_ctx = AuthenticatedRequestContext.model_validate(request)
functions, page_meta_info = await _functions_service.list_functions(
request.app,
user_id=req_ctx.user_id,
product_name=req_ctx.product_name,
pagination_limit=query_params.limit,
pagination_offset=query_params.offset,
order_by=OrderBy.model_construct(**query_params.order_by.model_dump()),
search_by_function_title=query_params.filters.search_by_title,
search_by_multi_columns=query_params.search,
)

chunk: list[RegisteredFunctionGet] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from typing import Annotated

from models_library.basic_types import IDStr
from models_library.functions import FunctionID
from models_library.groups import GroupID
from models_library.rest_base import RequestParameters
from models_library.rest_filters import Filters, FiltersQueryParameters
from models_library.rest_ordering import (
OrderBy,
OrderDirection,
create_ordering_query_model_class,
)
from models_library.rest_pagination import PageQueryParameters
from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, Field

from ...models import AuthenticatedRequestContext

Expand All @@ -17,14 +27,56 @@ class FunctionGroupPathParams(FunctionPathParams):
group_id: GroupID


class _FunctionQueryParams(BaseModel):
class FunctionQueryParams(BaseModel):
include_extras: bool = False


class FunctionGetQueryParams(_FunctionQueryParams): ...
class FunctionGetQueryParams(FunctionQueryParams): ...


class FunctionFilters(Filters):
search_by_title: Annotated[
str | None,
Field(
description="A search query to filter functions by their title. This field performs a case-insensitive partial match against the function title field.",
),
] = None


FunctionListOrderQueryParams: type[RequestParameters] = (
create_ordering_query_model_class(
ordering_fields={
"created_at",
"modified_at",
"name",
},
default=OrderBy(field=IDStr("modified_at"), direction=OrderDirection.DESC),
ordering_fields_api_to_column_map={
"created_at": "created",
"modified_at": "modified",
},
)
)


class FunctionsListExtraQueryParams(RequestParameters):
search: Annotated[
str | None,
Field(
description="Multi column full text search",
max_length=100,
examples=["My Function"],
),
] = None


class FunctionsListQueryParams(PageQueryParameters, _FunctionQueryParams): ...
class FunctionsListQueryParams(
PageQueryParameters,
FunctionListOrderQueryParams, # type: ignore[misc, valid-type]
FiltersQueryParameters[FunctionFilters],
FunctionsListExtraQueryParams,
FunctionQueryParams,
): ...


__all__: tuple[str, ...] = ("AuthenticatedRequestContext",)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import pytest
from aiohttp.test_utils import TestClient
from common_library.json_serialization import json_dumps
from models_library.api_schemas_webserver.functions import (
FunctionClass,
JSONFunctionInputSchema,
Expand Down Expand Up @@ -109,6 +110,12 @@ async def test_function_workflow(
assert returned_function.uid is not None
returned_function_uid = returned_function.uid

# Register a new function (duplicate)
url = client.app.router["register_function"].url_for()
mocked_function.update(title=mocked_function["title"] + " (duplicate)")
response = await client.post(url, json=mocked_function)
await assert_status(response, expected_status_code=expected_register)

# Get the registered function
url = client.app.router["get_function"].url_for(
function_id=f"{returned_function_uid}"
Expand All @@ -119,6 +126,69 @@ async def test_function_workflow(
retrieved_function = TypeAdapter(RegisteredFunctionGet).validate_python(data)
assert retrieved_function.uid == returned_function.uid

# List existing functions (default)
url = client.app.router["list_functions"].url_for()
response = await client.get(url)
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 2
assert returned_function_uid in [f.uid for f in retrieved_functions]
assert (
retrieved_functions[1].uid == returned_function_uid
) # ordered by modified_at by default

# List existing functions (ordered by created_at ascending)
url = client.app.router["list_functions"].url_for()
response = await client.get(
url,
params={"order_by": json_dumps({"field": "created_at", "direction": "asc"})},
)
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 2
assert returned_function_uid in [f.uid for f in retrieved_functions]
assert retrieved_functions[0].uid == returned_function_uid

# List existing functions (searching for not existing)
url = client.app.router["list_functions"].url_for()
response = await client.get(
url, params={"search": "you_can_not_find_me_because_I_do_not_exist"}
)
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 0

# List existing functions (searching for duplicate)
url = client.app.router["list_functions"].url_for()
response = await client.get(url, params={"search": "duplicate"})
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 1

# List existing functions (searching by title)
url = client.app.router["list_functions"].url_for()
response = await client.get(
url, params={"filters": json_dumps({"search_by_title": "duplicate"})}
)
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 1

# Set group permissions for other user
new_group_id = other_logged_user["primary_gid"]
new_group_access_rights = {"read": True, "write": True, "execute": False}
Expand Down Expand Up @@ -156,17 +226,6 @@ async def test_function_workflow(
new_group_id: new_group_access_rights
}

# List existing functions
url = client.app.router["list_functions"].url_for()
response = await client.get(url)
data, error = await assert_status(response, expected_list)
if not error:
retrieved_functions = TypeAdapter(list[RegisteredFunctionGet]).validate_python(
data
)
assert len(retrieved_functions) == 1
assert retrieved_functions[0].uid == returned_function_uid

# Update existing function
new_title = "Test Function (edited)"
new_description = "A test function (edited)"
Expand Down
Loading