Skip to content

feat: Create Pydantic models for Search Pipeline Run API#106

Open
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-query-paramsfrom
ycq/search-pipeline-run-pydantic-models
Open

feat: Create Pydantic models for Search Pipeline Run API#106
yuechao-qin wants to merge 1 commit intoycq/search-pipeline-run-query-paramsfrom
ycq/search-pipeline-run-pydantic-models

Conversation

@yuechao-qin
Copy link
Collaborator

@yuechao-qin yuechao-qin commented Feb 24, 2026

TL;DR

Added a new filter_query parameter to the pipeline runs API with Pydantic validation models, while maintaining backward compatibility with the existing filter parameter.

What's new?

JSON definition for filter_query:

{
  // logical (any predicate)
  "<and|or>": [
    // leaf
    {"key_exists": {"key": "<KEY>"}},
    // leaf
    {"value_contains": {"key": "<KEY>", "value_substring": "<VALUE>"}},
    // leaf
    {"value_in": {"key": "<KEY>", "values": ["<V1>", "<V2>", "..."]}},
    // leaf
    {"value_equals": {"key": "<KEY>", "value": "<VALUE>"}},
    // leaf
    {"time_range": {"key": "system/pipeline_run.date.created_at", "start_time": "<START_DATE>", "end_time": "<END_DATE>"}},
    // logical (leaf only)
    {"not": {"<LEAF_PREDICATE>": {}}},
    // logical (any predicate)
    {"<and|or>": [...]},
    ...
  ]
}

Example JSON for filter_query:

{
  "and": [
    {"key_exists": {"key": "team"}},
    {"value_equals": {"key": "env", "value": "prod"}},
    {"value_in": {"key": "region", "values": ["us-east", "us-west", "eu-west"]}},
    {"not": {"key_exists": {"key": "deprecated"}}},
    {"or": [{"value_contains": {"key": "name", "value_substring": "nightly"}}]}
  ]
}

What changed?

API GET /api/pipeline_runs/

Functional

  • New filter_query if used in API will return an HTTP 501 (unimplemented error)
  • Only filter or filter_query (mutual exclusive) can be in the API, else it returns a HTTP 422.

Other

  • Added a new filter_query parameter to ListPipelineRunsParams that accepts structured JSON queries
  • Created comprehensive Pydantic models in filter_query_models.py for validating filter queries, including:
    • Leaf predicates: key_exists, value_equals, value_contains, value_in, time_range
    • Logical operators: and, or, not
    • Recursive nesting support for complex queries
  • Implemented JSON validation for filter_query with proper error handling
  • Enforced strict validation rules including non-empty strings and timezone-aware datetimes

How to test?

uv run pytest tests/test_api_server_sql.py tests/test_filter_query_models.py
  • Test the new filter_query parameter with valid JSON structures like {"and": [{"key_exists": {"key": "team"}}]}
  • Verify that using both filter and filter_query simultaneously returns a 422 error
  • Confirm that invalid JSON in filter_query triggers proper validation errors
  • Check that valid filter_query usage returns a 501 "not yet implemented" response
  • Test edge cases like empty strings, missing timezone info, and nested logical operators

Why make this change?

This PR introduces the Pydantic models and API wiring that upstream PRs depend on to implement the Search Pipeline Run API.

Copy link
Collaborator

@Volv-G Volv-G left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, just 1 question on the design choice

@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-query-params to graphite-base/106 February 25, 2026 04:25
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from 78fdccf to f7dc9bc Compare February 25, 2026 05:27
@yuechao-qin yuechao-qin changed the base branch from graphite-base/106 to ycq/search-pipeline-run-query-params February 25, 2026 05:28
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from f7dc9bc to b5f0ba9 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-query-params branch from 44babd7 to e853104 Compare February 26, 2026 05:34
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-query-params branch from e853104 to f0a5c13 Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from b5f0ba9 to 88b265f Compare February 27, 2026 21:55
@yuechao-qin yuechao-qin changed the base branch from ycq/search-pipeline-run-query-params to graphite-base/106 February 28, 2026 07:20
@yuechao-qin yuechao-qin force-pushed the ycq/search-pipeline-run-pydantic-models branch from 88b265f to 9a0930e Compare February 28, 2026 10:21
@yuechao-qin yuechao-qin changed the base branch from graphite-base/106 to ycq/search-pipeline-run-query-params February 28, 2026 10:21

class ValueEquals(_BaseModel):
key: NonEmptyStr
value: NonEmptyStr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Maybe empty value is OK.

ambiguous timestamps that could silently resolve to the wrong timezone."""

key: NonEmptyStr
start_time: pydantic.AwareDatetime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start_time can be optional too


class TestFilterQuery:
def test_full_example_from_design_doc(self):
json_str = """
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want, you can construct Python dict and then use json.dumps to serialize it.
But JSON string like you have is OK too.

pass


class MutuallyExclusiveFilterError(ApiValidationError):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MutuallyExclusiveFilterError is pretty specific to pipeline run search. Maybe it should be declared in that module? Or you can just use ApiValidationError
Or if you envision more APIs with mutually exclusive parameters (we avoid this), you could rename the class to MutuallyExclusiveParametersError.

Copy link
Contributor

@Ark-kun Ark-kun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants