consolidate attribute filtering to match non-english and url encoded values (#22002)
Some checks failed
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled

This commit is contained in:
Josh Hawkins 2026-02-14 08:33:17 -06:00 committed by GitHub
parent 73c1e12faf
commit 4dcd2968b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 16 deletions

View File

@ -69,6 +69,25 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=[Tags.events]) router = APIRouter(tags=[Tags.events])
def _build_attribute_filter_clause(attributes: str):
filtered_attributes = [
attr.strip() for attr in attributes.split(",") if attr.strip()
]
attribute_clauses = []
for attr in filtered_attributes:
attribute_clauses.append(Event.data.cast("text") % f'*:"{attr}"*')
escaped_attr = json.dumps(attr, ensure_ascii=True)[1:-1]
if escaped_attr != attr:
attribute_clauses.append(Event.data.cast("text") % f'*:"{escaped_attr}"*')
if not attribute_clauses:
return None
return reduce(operator.or_, attribute_clauses)
@router.get( @router.get(
"/events", "/events",
response_model=list[EventResponse], response_model=list[EventResponse],
@ -193,13 +212,8 @@ def events(
if attributes != "all": if attributes != "all":
# Custom classification results are stored as data[model_name] = result_value # Custom classification results are stored as data[model_name] = result_value
filtered_attributes = attributes.split(",") attribute_clause = _build_attribute_filter_clause(attributes)
attribute_clauses = [] if attribute_clause is not None:
for attr in filtered_attributes:
attribute_clauses.append(Event.data.cast("text") % f'*:"{attr}"*')
attribute_clause = reduce(operator.or_, attribute_clauses)
clauses.append(attribute_clause) clauses.append(attribute_clause)
if recognized_license_plate != "all": if recognized_license_plate != "all":
@ -508,7 +522,7 @@ def events_search(
cameras = params.cameras cameras = params.cameras
labels = params.labels labels = params.labels
sub_labels = params.sub_labels sub_labels = params.sub_labels
attributes = params.attributes attributes = unquote(params.attributes)
zones = params.zones zones = params.zones
after = params.after after = params.after
before = params.before before = params.before
@ -607,13 +621,9 @@ def events_search(
if attributes != "all": if attributes != "all":
# Custom classification results are stored as data[model_name] = result_value # Custom classification results are stored as data[model_name] = result_value
filtered_attributes = attributes.split(",") attribute_clause = _build_attribute_filter_clause(attributes)
attribute_clauses = [] if attribute_clause is not None:
event_filters.append(attribute_clause)
for attr in filtered_attributes:
attribute_clauses.append(Event.data.cast("text") % f'*:"{attr}"*')
event_filters.append(reduce(operator.or_, attribute_clauses))
if zones != "all": if zones != "all":
zone_clauses = [] zone_clauses = []

View File

@ -168,6 +168,57 @@ class TestHttpApp(BaseTestHttp):
assert events[0]["id"] == id assert events[0]["id"] == id
assert events[1]["id"] == id2 assert events[1]["id"] == id2
def test_get_event_list_match_multilingual_attribute(self):
event_id = "123456.zh"
attribute = "中文标签"
with AuthTestClient(self.app) as client:
super().insert_mock_event(event_id, data={"custom_attr": attribute})
events = client.get("/events", params={"attributes": attribute}).json()
assert len(events) == 1
assert events[0]["id"] == event_id
events = client.get(
"/events", params={"attributes": "%E4%B8%AD%E6%96%87%E6%A0%87%E7%AD%BE"}
).json()
assert len(events) == 1
assert events[0]["id"] == event_id
def test_events_search_match_multilingual_attribute(self):
event_id = "123456.zh.search"
attribute = "中文标签"
mock_embeddings = Mock()
mock_embeddings.search_thumbnail.return_value = [(event_id, 0.05)]
self.app.frigate_config.semantic_search.enabled = True
self.app.embeddings = mock_embeddings
with AuthTestClient(self.app) as client:
super().insert_mock_event(event_id, data={"custom_attr": attribute})
events = client.get(
"/events/search",
params={
"search_type": "similarity",
"event_id": event_id,
"attributes": attribute,
},
).json()
assert len(events) == 1
assert events[0]["id"] == event_id
events = client.get(
"/events/search",
params={
"search_type": "similarity",
"event_id": event_id,
"attributes": "%E4%B8%AD%E6%96%87%E6%A0%87%E7%AD%BE",
},
).json()
assert len(events) == 1
assert events[0]["id"] == event_id
def test_get_good_event(self): def test_get_good_event(self):
id = "123456.random" id = "123456.random"