Skip to content

feat: API pipeline run list has execution summary#96

Open
yuechao-qin wants to merge 1 commit intoycq/function_get_execution_status_statsfrom
ycq/api_pipeline_run_list_has_ended
Open

feat: API pipeline run list has execution summary#96
yuechao-qin wants to merge 1 commit intoycq/function_get_execution_status_statsfrom
ycq/api_pipeline_run_list_has_ended

Conversation

@yuechao-qin
Copy link
Collaborator

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

TL;DR

Added execution summary to pipeline run responses to provide aggregated information about pipeline execution status (GET /api/pipeline_runs/)..

What changed?

  • Added execution_summary field to PipelineRunResponse dataclass
  • Renamed _get_execution_status_stats to _get_execution_stats_and_summary to return both stats and summary

How to test?

  1. Create a pipeline run with multiple nodes in different states
  2. Call the list API with include_execution_stats=True
  3. Verify the response contains both execution stats and execution summary
  4. Check that the summary correctly reports total nodes, ended nodes, and completion status

Run the new test cases in tests/test_api_server_sql.py:

pytest tests/test_api_server_sql.py

Why make this change?

This closes #87

image.png

Copy link
Collaborator Author

yuechao-qin commented Feb 17, 2026

@yuechao-qin yuechao-qin marked this pull request as ready for review February 17, 2026 17:40
@yuechao-qin yuechao-qin requested a review from Ark-kun as a code owner February 17, 2026 17:40
@yuechao-qin yuechao-qin changed the base branch from ycq/function_get_execution_status_stats to graphite-base/96 February 18, 2026 02:26
@yuechao-qin yuechao-qin force-pushed the ycq/api_pipeline_run_list_has_ended branch from ea578e3 to 489c797 Compare February 18, 2026 02:26
@yuechao-qin yuechao-qin changed the base branch from graphite-base/96 to ycq/function_get_execution_status_stats February 18, 2026 02:27
@yuechao-qin yuechao-qin changed the base branch from ycq/function_get_execution_status_stats to graphite-base/96 February 20, 2026 04:27
@yuechao-qin yuechao-qin force-pushed the ycq/api_pipeline_run_list_has_ended branch from 489c797 to 3df5bde Compare February 20, 2026 04:28
@yuechao-qin yuechao-qin changed the base branch from graphite-base/96 to ycq/function_get_execution_status_stats February 20, 2026 04:28
@yuechao-qin yuechao-qin force-pushed the ycq/function_get_execution_status_stats branch 2 times, most recently from e382bc8 to 5ae141c Compare February 21, 2026 02:41
@yuechao-qin yuechao-qin force-pushed the ycq/api_pipeline_run_list_has_ended branch from 3df5bde to 5014f69 Compare February 21, 2026 02:41
if status in bts.CONTAINER_STATUSES_ENDED:
self.ended_executions += count

self.has_ended = self.ended_executions == self.total_executions
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a pattern I would typically try to avoid in other programming languages. Here we are creating a an instance of the class with some initial state, then calling a function on that instance to later on to hydrate the instance with some values, to then return the instance to a caller.

What I would strive to due instead is have a pure / read only model that doesn't write to itself, or get written to after instatiation, but instead gets instantiated with its final values in the constructor. In this case, you could have a function build_execution_status_summar_from_stats that calculates the model attributes, instantiates the model, and returns the model in a read only state.

I could give examples of how this would look in other languages but that wouldn't be helpful.

Here is what Gemini says about implementing that in Python in a "pythonic" way:

@dataclasses.dataclass(frozen=True) # frozen=True makes instances immutable
class ExecutionStatusSummary:
    """A read-only summary of execution statuses."""
    total_executions: int
    ended_executions: int
    has_ended: bool

    @classmethod
    def from_stats(
        cls, stats: Dict[bts.ContainerExecutionStatus, int]
    ) -> "ExecutionStatusSummary":
        """
        Calculates summary attributes from a stats dictionary and returns a new
        immutable instance.
        """
        total_executions = sum(stats.values())

        ended_executions = sum(
            count
            for status, count in stats.items()
            if status in bts.CONTAINER_STATUSES_ENDED
        )

        # This logic is more robust: an empty set of executions hasn't "ended".
        has_ended = total_executions > 0 and ended_executions == total_executions

        return cls(
            total_executions=total_executions,
            ended_executions=ended_executions,
            has_ended=has_ended,
        )

and consuming it as:

summary = ExecutionStatusSummary.from_stats(stats)

Copy link
Collaborator

@morgan-wowk morgan-wowk Feb 24, 2026

Choose a reason for hiding this comment

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

This is not based on your exact changes and just an example ☝️ . In other words, not a copy/paste replacement and just a suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer less mutation too.
We've discussed this case with Yue Chao and the main reason for mutating implementation is because the instance will be constructed from multiple status stats objects.
Although, what we could do in that case is to first combine multiple dicts into one (via a new function or collection.Counter) and then initialize the structure.

full_status_stats = collections.Counter()
for status_stats in list_of_status_stats:
    full_status_stats.update(status_stats)
ExecutionStatusSummary.from_status_stats(full_status_stats)

summary = ExecutionStatusSummary()
status_stats: dict[str, int] = {}
for status, count in stats.items():
summary.count_execution_status(status=status, count=count)
Copy link
Collaborator

Choose a reason for hiding this comment

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

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