Add wildcard and multi-value support for tag/search filters (#18)
- Tag filters now support wildcards (*) converted to SQL LIKE (%) - Tag filters now support comma-separated multiple values - Applied to /api/v1/artifacts, /api/v1/tags, and /api/v1/uploads endpoints
This commit is contained in:
@@ -2593,7 +2593,10 @@ def list_package_artifacts(
|
||||
def list_all_artifacts(
|
||||
project: Optional[str] = Query(None, description="Filter by project name"),
|
||||
package: Optional[str] = Query(None, description="Filter by package name"),
|
||||
tag: Optional[str] = Query(None, description="Filter by tag name"),
|
||||
tag: Optional[str] = Query(
|
||||
None,
|
||||
description="Filter by tag name. Supports wildcards (*) and comma-separated values",
|
||||
),
|
||||
content_type: Optional[str] = Query(None, description="Filter by content type"),
|
||||
min_size: Optional[int] = Query(None, ge=0, description="Minimum size in bytes"),
|
||||
max_size: Optional[int] = Query(None, ge=0, description="Maximum size in bytes"),
|
||||
@@ -2628,7 +2631,26 @@ def list_all_artifacts(
|
||||
if package:
|
||||
tag_query = tag_query.filter(Package.name == package)
|
||||
if tag:
|
||||
tag_query = tag_query.filter(Tag.name == tag)
|
||||
# Support multiple values (comma-separated) and wildcards (*)
|
||||
tag_values = [t.strip() for t in tag.split(",") if t.strip()]
|
||||
if len(tag_values) == 1:
|
||||
tag_val = tag_values[0]
|
||||
if "*" in tag_val:
|
||||
# Wildcard: convert * to SQL LIKE %
|
||||
tag_query = tag_query.filter(
|
||||
Tag.name.ilike(tag_val.replace("*", "%"))
|
||||
)
|
||||
else:
|
||||
tag_query = tag_query.filter(Tag.name == tag_val)
|
||||
else:
|
||||
# Multiple values: check if any match (with wildcard support)
|
||||
tag_conditions = []
|
||||
for tag_val in tag_values:
|
||||
if "*" in tag_val:
|
||||
tag_conditions.append(Tag.name.ilike(tag_val.replace("*", "%")))
|
||||
else:
|
||||
tag_conditions.append(Tag.name == tag_val)
|
||||
tag_query = tag_query.filter(or_(*tag_conditions))
|
||||
artifact_ids = tag_query.distinct().subquery()
|
||||
query = query.filter(Artifact.id.in_(artifact_ids))
|
||||
|
||||
@@ -2722,7 +2744,10 @@ def list_all_artifacts(
|
||||
def list_all_tags(
|
||||
project: Optional[str] = Query(None, description="Filter by project name"),
|
||||
package: Optional[str] = Query(None, description="Filter by package name"),
|
||||
search: Optional[str] = Query(None, description="Search by tag name"),
|
||||
search: Optional[str] = Query(
|
||||
None,
|
||||
description="Search by tag name. Supports wildcards (*) and comma-separated values",
|
||||
),
|
||||
from_date: Optional[datetime] = Query(
|
||||
None, alias="from", description="Created after"
|
||||
),
|
||||
@@ -2749,7 +2774,24 @@ def list_all_tags(
|
||||
if package:
|
||||
query = query.filter(Package.name == package)
|
||||
if search:
|
||||
query = query.filter(Tag.name.ilike(f"%{search}%"))
|
||||
# Support multiple values (comma-separated) and wildcards (*)
|
||||
search_values = [s.strip() for s in search.split(",") if s.strip()]
|
||||
if len(search_values) == 1:
|
||||
search_val = search_values[0]
|
||||
if "*" in search_val:
|
||||
query = query.filter(Tag.name.ilike(search_val.replace("*", "%")))
|
||||
else:
|
||||
query = query.filter(Tag.name.ilike(f"%{search_val}%"))
|
||||
else:
|
||||
search_conditions = []
|
||||
for search_val in search_values:
|
||||
if "*" in search_val:
|
||||
search_conditions.append(
|
||||
Tag.name.ilike(search_val.replace("*", "%"))
|
||||
)
|
||||
else:
|
||||
search_conditions.append(Tag.name.ilike(f"%{search_val}%"))
|
||||
query = query.filter(or_(*search_conditions))
|
||||
if from_date:
|
||||
query = query.filter(Tag.created_at >= from_date)
|
||||
if to_date:
|
||||
@@ -4011,7 +4053,10 @@ def list_all_uploads(
|
||||
None, description="Filter by deduplication status"
|
||||
),
|
||||
search: Optional[str] = Query(None, description="Search by original filename"),
|
||||
tag: Optional[str] = Query(None, description="Filter by tag name"),
|
||||
tag: Optional[str] = Query(
|
||||
None,
|
||||
description="Filter by tag name. Supports wildcards (*) and comma-separated values",
|
||||
),
|
||||
sort: Optional[str] = Query(
|
||||
None, description="Sort field: uploaded_at, original_name, size"
|
||||
),
|
||||
@@ -4055,7 +4100,24 @@ def list_all_uploads(
|
||||
if search:
|
||||
query = query.filter(Upload.original_name.ilike(f"%{search}%"))
|
||||
if tag:
|
||||
query = query.filter(Upload.tag_name == tag)
|
||||
# Support multiple values (comma-separated) and wildcards (*)
|
||||
tag_values = [t.strip() for t in tag.split(",") if t.strip()]
|
||||
if len(tag_values) == 1:
|
||||
tag_val = tag_values[0]
|
||||
if "*" in tag_val:
|
||||
query = query.filter(Upload.tag_name.ilike(tag_val.replace("*", "%")))
|
||||
else:
|
||||
query = query.filter(Upload.tag_name == tag_val)
|
||||
else:
|
||||
tag_conditions = []
|
||||
for tag_val in tag_values:
|
||||
if "*" in tag_val:
|
||||
tag_conditions.append(
|
||||
Upload.tag_name.ilike(tag_val.replace("*", "%"))
|
||||
)
|
||||
else:
|
||||
tag_conditions.append(Upload.tag_name == tag_val)
|
||||
query = query.filter(or_(*tag_conditions))
|
||||
|
||||
# Validate and apply sorting
|
||||
valid_sort_fields = {
|
||||
|
||||
Reference in New Issue
Block a user