Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions pkgs/standards/tigrbl/tests/unit/test_handlers_list_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

import pytest

from tigrbl import TigrblApp
from tigrbl.engine.shortcuts import engine as build_engine, mem
from tigrbl.specs import F, IO, S, acol
from tigrbl.table import Base
from tigrbl.types import Integer, String


@pytest.mark.asyncio
async def test_list_core_respects_nested_filter_payloads():
class Thing(Base):
__tablename__ = "list_filter_things"

id = acol(
storage=S(type_=Integer, primary_key=True, autoincrement=True),
field=F(py_type=int),
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
)
name = acol(
storage=S(type_=String(40), nullable=False),
field=F(py_type=str),
io=IO(
in_verbs=("create",),
out_verbs=("read", "list"),
filter_ops=("eq", "ilike"),
),
)

cfg = mem()
app = TigrblApp(engine=build_engine(cfg))
app.include_model(Thing)
await app.initialize()

async with app.engine.asession() as session:
await Thing.handlers.create.core({"db": session, "payload": {"name": "alpha"}})
await Thing.handlers.create.core({"db": session, "payload": {"name": "bravo"}})

rows = await Thing.handlers.list.core(
{"db": session, "payload": {"filters": {"name": "bravo"}}}
)
assert [row.name for row in rows] == ["bravo"]

rows = await Thing.handlers.list.core(
{"db": session, "payload": {"filters": {"name__ilike": "%a%"}}}
)
assert sorted(row.name for row in rows) == ["alpha", "bravo"]
58 changes: 53 additions & 5 deletions pkgs/standards/tigrbl/tigrbl/bindings/handlers/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import inspect
from functools import lru_cache
from typing import Any, Callable, Mapping, Optional
from typing import Any, Callable, Mapping, Optional, Sequence

from ... import core as _core
from ...op import OpSpec
Expand All @@ -24,10 +24,56 @@ async def _call_list_core(
payload: Mapping[str, Any],
ctx: Mapping[str, Any],
):
filters = dict(payload) if isinstance(payload, Mapping) else {}
skip = filters.pop("skip", None)
limit = filters.pop("limit", None)
filters_arg = filters if filters else None
params = dict(payload) if isinstance(payload, Mapping) else {}
skip = params.pop("skip", None)
limit = params.pop("limit", None)
raw_sort = params.pop("sort", None)

raw_filters = params.pop("filters", None)
filters_arg: Any = None

if isinstance(raw_filters, Mapping):
filters_arg = dict(raw_filters)
elif raw_filters is not None:
filters_arg = raw_filters

if params:
extras = dict(params)
if isinstance(filters_arg, Mapping):
merged = dict(filters_arg)
merged.update(extras)
filters_arg = merged
elif filters_arg is None:
filters_arg = extras

if isinstance(filters_arg, Mapping) and not filters_arg:
filters_arg = None

def _normalize_sort_entry(value: Any) -> str | None:
if isinstance(value, Mapping):
field = value.get("field")
if not field:
return None
direction = str(value.get("direction", "")).strip().lower()
if direction in ("desc", "descending", "-1"):
return f"{field}:desc"
if direction in ("asc", "ascending", "1", ""):
return str(field)
return str(field)
if isinstance(value, str):
return value
return None

sort_arg: Any = None
if isinstance(raw_sort, Mapping):
sort_arg = _normalize_sort_entry(raw_sort)
elif isinstance(raw_sort, Sequence) and not isinstance(
raw_sort, (str, bytes, bytearray)
):
tokens = [token for item in raw_sort if (token := _normalize_sort_entry(item))]
sort_arg = tokens if tokens else None
elif raw_sort is not None:
sort_arg = raw_sort

db = _ctx_db(ctx)
req = _ctx_request(ctx)
Expand All @@ -54,6 +100,8 @@ def add_candidate(
kwargs["skip"] = skip
if limit is not None:
kwargs["limit"] = limit
if sort_arg is not None:
kwargs["sort"] = sort_arg
candidates.append((args, kwargs))

add_candidate(False, False, True, True)
Expand Down
Loading