-
Notifications
You must be signed in to change notification settings - Fork 6
Post Action Plugin Infrastructure #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ff59341
b9b8e92
e71a08b
5a93be8
3cc2d56
98d77e2
b845a9d
df9a43d
4ad66c7
99487cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,173 @@ | ||||||||||||||||||||||||||||||||||||||
| ############################################################################### | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # MIT License | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Copyright (c) 2026 Advanced Micro Devices, Inc. | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||||||||||||||||||||||||||||||||||
| # of this software and associated documentation files (the "Software"), to deal | ||||||||||||||||||||||||||||||||||||||
| # in the Software without restriction, including without limitation the rights | ||||||||||||||||||||||||||||||||||||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||||||||||||||||||||||||||||||||||
| # copies of the Software, and to permit persons to whom the Software is | ||||||||||||||||||||||||||||||||||||||
| # furnished to do so, subject to the following conditions: | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # The above copyright notice and this permission notice shall be included in all | ||||||||||||||||||||||||||||||||||||||
| # copies or substantial portions of the Software. | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||||||||||||||||||||||||||||||||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||||||||||||||||||||||||||||||||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||||||||||||||||||||||||||||||||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||||||||||||||||||||||||||||||||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||||||||||||||||||||||||||||||||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||||||||||||||||||||||||||||||||||
| # SOFTWARE. | ||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||
| ############################################################################### | ||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||
| from typing import TYPE_CHECKING, Any, Optional | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel, field_validator | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from nodescraper.enums import EventPriority, ExecutionStatus | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||||||||||||||||||||||
| from nodescraper.models.event import Event | ||||||||||||||||||||||||||||||||||||||
| from nodescraper.models.pluginresult import PluginResult | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| class PostActionCondition(BaseModel): | ||||||||||||||||||||||||||||||||||||||
| """A single condition that, if matched, causes a post-action plugin to run. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| All specified (non-None) fields are AND'd together within one condition. | ||||||||||||||||||||||||||||||||||||||
| Unspecified fields are ignored and never prevent a match. A list of | ||||||||||||||||||||||||||||||||||||||
| ``PostActionCondition`` objects is OR'd by the containing | ||||||||||||||||||||||||||||||||||||||
| :class:`PostActionPluginConfig`. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| plugin: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||
| """If set, only inspect the PluginResult whose ``source`` matches this name. | ||||||||||||||||||||||||||||||||||||||
| If None, all results are candidates.""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| status: Optional[ExecutionStatus] = None | ||||||||||||||||||||||||||||||||||||||
| """If set, the result's ExecutionStatus must be >= this value. | ||||||||||||||||||||||||||||||||||||||
| Accepts an :class:`~nodescraper.enums.ExecutionStatus` member or its name as | ||||||||||||||||||||||||||||||||||||||
| a string (e.g. ``"WARNING"``, ``"ERROR"``, ``"EXECUTION_FAILURE"``).""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| event_category: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||
| """If set, at least one event from analysis_result or collection_result must | ||||||||||||||||||||||||||||||||||||||
| have a category equal to this value (matched after the same normalisation | ||||||||||||||||||||||||||||||||||||||
| applied to event categories: strip, upper, spaces/hyphens → underscores).""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| event_priority: Optional[EventPriority] = None | ||||||||||||||||||||||||||||||||||||||
| """If set, at least one event's priority must be >= this value. | ||||||||||||||||||||||||||||||||||||||
| Accepts an :class:`~nodescraper.enums.EventPriority` member or its name as | ||||||||||||||||||||||||||||||||||||||
| a string (e.g. ``"WARNING"``, ``"ERROR"``, ``"CRITICAL"``).""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| event_description_contains: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||
| """If set, at least one event's description must contain this substring | ||||||||||||||||||||||||||||||||||||||
| (case-sensitive).""" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the types are fixed, i recommend you add these too:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 3cc2d56 |
||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
| # Field validators — allow string names from JSON configs | ||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @field_validator("status", mode="before") | ||||||||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||||||||
| def validate_status(cls, v: Any) -> Optional[ExecutionStatus]: | ||||||||||||||||||||||||||||||||||||||
| """Accept an ExecutionStatus member or its name string.""" | ||||||||||||||||||||||||||||||||||||||
| if v is None or isinstance(v, ExecutionStatus): | ||||||||||||||||||||||||||||||||||||||
| return v | ||||||||||||||||||||||||||||||||||||||
| if isinstance(v, str): | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| return ExecutionStatus[v.upper()] | ||||||||||||||||||||||||||||||||||||||
| except KeyError as e: | ||||||||||||||||||||||||||||||||||||||
| raise ValueError(f"Unknown ExecutionStatus name: {v!r}") from e | ||||||||||||||||||||||||||||||||||||||
| return v | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @field_validator("event_priority", mode="before") | ||||||||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||||||||
| def validate_event_priority(cls, v: Any) -> Optional[EventPriority]: | ||||||||||||||||||||||||||||||||||||||
| """Accept an EventPriority member or its name string.""" | ||||||||||||||||||||||||||||||||||||||
| if v is None or isinstance(v, EventPriority): | ||||||||||||||||||||||||||||||||||||||
| return v | ||||||||||||||||||||||||||||||||||||||
| if isinstance(v, str): | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| return EventPriority[v.upper()] | ||||||||||||||||||||||||||||||||||||||
| except KeyError as e: | ||||||||||||||||||||||||||||||||||||||
| raise ValueError(f"Unknown EventPriority name: {v!r}") from e | ||||||||||||||||||||||||||||||||||||||
| return v | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
| # Internal helpers | ||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||||||||||
| def _normalise_category(raw: str) -> str: | ||||||||||||||||||||||||||||||||||||||
| """Apply the same normalisation used by :class:`~nodescraper.models.event.Event`.""" | ||||||||||||||||||||||||||||||||||||||
| normalised = str(raw).strip().upper() | ||||||||||||||||||||||||||||||||||||||
| return re.sub(r"[\s-]", "_", normalised) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def _get_all_events(self, result: PluginResult) -> list[Event]: | ||||||||||||||||||||||||||||||||||||||
| """Collect events from both collection and analysis task results.""" | ||||||||||||||||||||||||||||||||||||||
| events: list[Event] = [] | ||||||||||||||||||||||||||||||||||||||
| rd = result.result_data | ||||||||||||||||||||||||||||||||||||||
| if rd is None: | ||||||||||||||||||||||||||||||||||||||
| return events | ||||||||||||||||||||||||||||||||||||||
| if hasattr(rd, "collection_result") and rd.collection_result is not None: | ||||||||||||||||||||||||||||||||||||||
| events.extend(rd.collection_result.events) | ||||||||||||||||||||||||||||||||||||||
| if hasattr(rd, "analysis_result") and rd.analysis_result is not None: | ||||||||||||||||||||||||||||||||||||||
| events.extend(rd.analysis_result.events) | ||||||||||||||||||||||||||||||||||||||
| return events | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def _matches_result(self, result: PluginResult) -> bool: | ||||||||||||||||||||||||||||||||||||||
| """Return True if *result* satisfies all specified fields (AND logic). | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Each field that is not None must be satisfied; unset fields are skipped. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| # --- status check --- | ||||||||||||||||||||||||||||||||||||||
| if self.status is not None: | ||||||||||||||||||||||||||||||||||||||
| if result.status < self.status: | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+124
to
+132
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. once the correct type is set for status this function can probably just be:
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 3cc2d56 |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Remaining checks all operate on events; collect them once. | ||||||||||||||||||||||||||||||||||||||
| events = self._get_all_events(result) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # --- event_category check --- | ||||||||||||||||||||||||||||||||||||||
| if self.event_category is not None: | ||||||||||||||||||||||||||||||||||||||
| normalised = self._normalise_category(self.event_category) | ||||||||||||||||||||||||||||||||||||||
| if not any(e.category == normalised for e in events): | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # --- event_priority check --- | ||||||||||||||||||||||||||||||||||||||
| if self.event_priority is not None: | ||||||||||||||||||||||||||||||||||||||
| if not any(e.priority >= self.event_priority for e in events): | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # --- event_description_contains check --- | ||||||||||||||||||||||||||||||||||||||
| if self.event_description_contains is not None: | ||||||||||||||||||||||||||||||||||||||
| if not any(self.event_description_contains in e.description for e in events): | ||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
| # Public API | ||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def is_met(self, plugin_results: list[PluginResult]) -> bool: | ||||||||||||||||||||||||||||||||||||||
| """Return True if this condition is satisfied by any of the provided results. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| If ``plugin`` is set only that plugin's result is checked; otherwise all | ||||||||||||||||||||||||||||||||||||||
| results are candidates. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||
| plugin_results: List of :class:`~nodescraper.models.pluginresult.PluginResult` | ||||||||||||||||||||||||||||||||||||||
| objects from the primary plugin run. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||
| bool: True if at least one candidate result satisfies all specified fields. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| candidates = [r for r in plugin_results if self.plugin is None or r.source == self.plugin] | ||||||||||||||||||||||||||||||||||||||
| return any(self._matches_result(r) for r in candidates) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should also be EventPriority not str
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming you meant
event_prioritywith this comment, which is fixed in 3cc2d56