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_at","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 @@ -17,6 +17,7 @@
RegisteredSolverFunction,
)
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 @@ -40,6 +41,7 @@
from .._services_metadata.proxy import ServiceMetadata
from ._functions_rest_exceptions import handle_rest_requests_exceptions
from ._functions_rest_schemas import (
FunctionFilters,
FunctionGetQueryParams,
FunctionPathParams,
FunctionsListQueryParams,
Expand Down Expand Up @@ -157,13 +159,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,6 +1,16 @@
from typing import Annotated

from models_library.basic_types import IDStr
from models_library.functions import FunctionID
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 @@ -12,14 +22,56 @@ class FunctionPathParams(BaseModel):
model_config = ConfigDict(populate_by_name=True, extra="forbid")


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 @@ -4,6 +4,7 @@
# pylint: disable=too-many-arguments


import json
from collections.abc import AsyncIterator
from http import HTTPStatus
from typing import Any
Expand Down Expand Up @@ -108,6 +109,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 @@ -118,17 +125,69 @@ async def test_function_workflow(
retrieved_function = TypeAdapter(RegisteredFunctionGet).validate_python(data)
assert retrieved_function.uid == returned_function.uid

# List existing functions
# 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) == 1
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

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