mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
Follow-up to #105171, which added `ruff` with a deliberately narrow, bug-class-only rule set and left the unused-name and star-import rules off pending a dedicated cleanup pass. This enables that second batch in `pyproject.toml` and fixes every violation across the tree (the bulk in integration tests): - `F401` unused-import and `F841` unused-variable: removed, preserving any side-effecting right-hand side (e.g. `result = node.query(...)` becomes `node.query(...)`). Two `try: import ...` feature probes are kept with `# noqa: F401`. - `F541` f-string-missing-placeholders: dropped the redundant `f` prefix. - `F403`/`F405` star imports: every `from module import *` is expanded into explicit imports of the names actually used, taken from each name's real origin, so stdlib/third-party names import from their real source rather than through a test "convenience bundle". The now-empty `tests/integration/helpers/kafka/common_direct.py` bundle is removed and its three remaining consumers import `_VarintBytes`/`KafkaAdminClient` directly. Eliminating the star imports surfaced a real bug the wildcard had masked from `F821`: `generate_presets.py` in `test_prometheus_protocols` called an undefined `execute_instant_query_with_http_api`; fixed to the existing `execute_query_via_http_api`. No `black`/`isort` reformatting was applied (neither is enforced in the style check), so the diff is limited to the lint fixes themselves. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
444 lines
18 KiB
Python
444 lines
18 KiB
Python
import dataclasses
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from typing import Any, Dict, List, Optional, Type
|
|
|
|
from . import Job, Workflow
|
|
from .settings import Settings
|
|
from .utils import MetaClasses, Shell, T, Utils
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class _Environment(MetaClasses.Serializable):
|
|
WORKFLOW_NAME: str
|
|
JOB_NAME: str
|
|
REPOSITORY: str
|
|
BRANCH: str
|
|
SHA: str
|
|
PR_NUMBER: int
|
|
EVENT_TYPE: str
|
|
EVENT_TIME: str
|
|
JOB_OUTPUT_STREAM: str
|
|
EVENT_FILE_PATH: str
|
|
CHANGE_URL: str
|
|
COMMIT_URL: str
|
|
BASE_BRANCH: str
|
|
RUN_ID: str
|
|
RUN_URL: str
|
|
INSTANCE_TYPE: str
|
|
INSTANCE_ID: str
|
|
INSTANCE_LIFE_CYCLE: str
|
|
PR_BODY: str
|
|
PR_TITLE: str
|
|
USER_LOGIN: str
|
|
FORK_NAME: str
|
|
COMMIT_MESSAGE: str = ""
|
|
# merged PR for "push" or "merge_group" workflow
|
|
LINKED_PR_NUMBER: int = 0
|
|
LOCAL_RUN: bool = False
|
|
PR_LABELS: List[str] = dataclasses.field(default_factory=list)
|
|
REPORT_MESSAGES: List[Dict[str, str]] = dataclasses.field(default_factory=list)
|
|
JOB_CONFIG: Optional[Job.Config] = None
|
|
TRACEBACKS: List[str] = dataclasses.field(default_factory=list)
|
|
WORKFLOW_JOB_DATA: Dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
JOB_KV_DATA: Dict[str, Any] = dataclasses.field(default_factory=dict)
|
|
# Keys present in JOB_KV_DATA when the job started, i.e. inherited from the
|
|
# initial (config) job. Used in Runner._post_run to emit only the KV data a
|
|
# job itself added as the job's `data` output, instead of re-emitting the
|
|
# whole inherited bucket into every job's output (toJson(needs)).
|
|
JOB_KV_DATA_BASE_KEYS: List[str] = dataclasses.field(default_factory=list)
|
|
COMMIT_AUTHORS: List[str] = dataclasses.field(default_factory=list)
|
|
WORKFLOW_CONFIG: Optional[Dict[str, Any]] = None
|
|
name = "environment"
|
|
|
|
@classmethod
|
|
def _load_workflow_job_data(cls) -> dict:
|
|
if Path(Settings.WORKFLOW_JOB_FILE).is_file():
|
|
with open(Settings.WORKFLOW_JOB_FILE, "r", encoding="utf8") as f:
|
|
return json.load(f)
|
|
print(
|
|
f"NOTE: Workflow job file [{Settings.WORKFLOW_JOB_FILE}] does not exist"
|
|
)
|
|
return {}
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "_Environment":
|
|
WORKFLOW_NAME = os.getenv("GITHUB_WORKFLOW", "")
|
|
JOB_NAME = os.getenv("JOB_NAME", "")
|
|
REPOSITORY = os.getenv("GITHUB_REPOSITORY", "")
|
|
# GITHUB_HEAD_REF for pull_request, GITHUB_REF_NAME for push
|
|
BRANCH = os.getenv("GITHUB_HEAD_REF", "") or os.getenv("GITHUB_REF_NAME", "")
|
|
|
|
EVENT_FILE_PATH = os.getenv("GITHUB_EVENT_PATH", "")
|
|
JOB_OUTPUT_STREAM = os.getenv("GITHUB_OUTPUT", "")
|
|
RUN_ID = os.getenv("GITHUB_RUN_ID", "0")
|
|
RUN_URL = f"https://github.com/{REPOSITORY}/actions/runs/{RUN_ID}"
|
|
BASE_BRANCH = os.getenv("GITHUB_BASE_REF", "")
|
|
USER_LOGIN = ""
|
|
COMMIT_AUTHORS = []
|
|
FORK_NAME = REPOSITORY
|
|
PR_BODY = ""
|
|
PR_TITLE = ""
|
|
PR_LABELS = []
|
|
LINKED_PR_NUMBER = 0
|
|
EVENT_TIME = ""
|
|
COMMIT_MESSAGE = ""
|
|
|
|
WORKFLOW_JOB_DATA = cls._load_workflow_job_data()
|
|
|
|
if EVENT_FILE_PATH:
|
|
with open(EVENT_FILE_PATH, "r", encoding="utf-8") as f:
|
|
github_event = json.load(f)
|
|
if "pull_request" in github_event:
|
|
FORK_NAME = github_event["pull_request"]["head"]["repo"]["full_name"]
|
|
EVENT_TYPE = Workflow.Event.PULL_REQUEST
|
|
PR_NUMBER = int(github_event["pull_request"]["number"])
|
|
SHA = github_event["pull_request"]["head"]["sha"]
|
|
CHANGE_URL = github_event["pull_request"]["html_url"]
|
|
COMMIT_URL = CHANGE_URL + f"/commits/{SHA}"
|
|
PR_BODY = github_event["pull_request"]["body"]
|
|
PR_TITLE = github_event["pull_request"]["title"]
|
|
PR_LABELS = [
|
|
label["name"] for label in github_event["pull_request"]["labels"]
|
|
]
|
|
USER_LOGIN = github_event["pull_request"]["user"]["login"]
|
|
EVENT_TIME = github_event.get("pull_request", {}).get(
|
|
"updated_at", None
|
|
)
|
|
elif "commits" in github_event:
|
|
EVENT_TYPE = Workflow.Event.PUSH
|
|
SHA = github_event["after"]
|
|
CHANGE_URL = github_event["head_commit"]["url"] # commit url
|
|
PR_NUMBER = 0
|
|
COMMIT_URL = CHANGE_URL
|
|
commit_message = github_event["head_commit"]["message"]
|
|
parts = commit_message.split(
|
|
"pull request #"
|
|
) # Merge pull request #77106 from XXXX
|
|
if len(parts) >= 2:
|
|
pr_str = parts[1].split()[0]
|
|
LINKED_PR_NUMBER = int(pr_str) if pr_str.isdigit() else 0
|
|
EVENT_TIME = github_event.get("repository", {}).get("updated_at", None)
|
|
commit_authors = set()
|
|
for commit in github_event["commits"]:
|
|
email = commit["author"]["email"]
|
|
# Validate email contains @ symbol
|
|
if email and "@" in email:
|
|
commit_authors.add(email)
|
|
COMMIT_AUTHORS = list(commit_authors)
|
|
elif "schedule" in github_event:
|
|
EVENT_TYPE = Workflow.Event.SCHEDULE
|
|
SHA = os.getenv(
|
|
"GITHUB_SHA", "0000000000000000000000000000000000000000"
|
|
)
|
|
PR_NUMBER = 0
|
|
CHANGE_URL = (
|
|
github_event["repository"]["html_url"] + "/commit/" + SHA
|
|
) # commit url
|
|
COMMIT_URL = CHANGE_URL
|
|
elif "inputs" in github_event:
|
|
# assume this is a dispatch
|
|
EVENT_TYPE = Workflow.Event.DISPATCH
|
|
SHA = os.getenv(
|
|
"GITHUB_SHA", "0000000000000000000000000000000000000000"
|
|
)
|
|
PR_NUMBER = 0
|
|
CHANGE_URL = (
|
|
github_event["repository"]["html_url"] + "/commit/" + SHA
|
|
) # commit url
|
|
COMMIT_URL = CHANGE_URL
|
|
elif "merge_group" in github_event:
|
|
PR_NUMBER = 0
|
|
EVENT_TYPE = Workflow.Event.MERGE_QUEUE
|
|
SHA = os.getenv(
|
|
"GITHUB_SHA", "0000000000000000000000000000000000000000"
|
|
)
|
|
head_ref = github_event.get("merge_group", {}).get("head_ref", "")
|
|
try:
|
|
if "/pr-" in head_ref:
|
|
pr_number_part = head_ref.split("/pr-")[1].split("-")[0]
|
|
LINKED_PR_NUMBER = (
|
|
int(pr_number_part) if pr_number_part.isdigit() else 0
|
|
)
|
|
CHANGE_URL = (
|
|
f"{github_event['repository']['html_url']}/pull/{LINKED_PR_NUMBER}"
|
|
if LINKED_PR_NUMBER
|
|
else ""
|
|
)
|
|
except:
|
|
LINKED_PR_NUMBER = 0
|
|
CHANGE_URL = ""
|
|
|
|
COMMIT_URL = CHANGE_URL
|
|
else:
|
|
assert False, "TODO: not supported"
|
|
|
|
INSTANCE_TYPE = (
|
|
os.getenv("INSTANCE_TYPE", None)
|
|
or Shell.get_output("ec2metadata --instance-type")
|
|
or ""
|
|
)
|
|
INSTANCE_ID = (
|
|
os.getenv("INSTANCE_ID", None)
|
|
or Shell.get_output("ec2metadata --instance-id")
|
|
or ""
|
|
)
|
|
INSTANCE_LIFE_CYCLE = (
|
|
os.getenv("INSTANCE_LIFE_CYCLE", None)
|
|
or Shell.get_output(
|
|
"curl -s --fail http://169.254.169.254/latest/meta-data/instance-life-cycle"
|
|
)
|
|
or ""
|
|
)
|
|
|
|
else:
|
|
print("WARNING: Local execution - dummy Environment will be generated")
|
|
SHA = "TEST"
|
|
PR_NUMBER = -1
|
|
EVENT_TYPE = Workflow.Event.PUSH
|
|
CHANGE_URL = ""
|
|
COMMIT_URL = ""
|
|
COMMIT_MESSAGE = ""
|
|
INSTANCE_TYPE = ""
|
|
INSTANCE_ID = ""
|
|
INSTANCE_LIFE_CYCLE = ""
|
|
|
|
return _Environment(
|
|
WORKFLOW_NAME=WORKFLOW_NAME,
|
|
JOB_NAME=JOB_NAME,
|
|
REPOSITORY=REPOSITORY,
|
|
BRANCH=BRANCH,
|
|
EVENT_FILE_PATH=EVENT_FILE_PATH,
|
|
JOB_OUTPUT_STREAM=JOB_OUTPUT_STREAM,
|
|
SHA=SHA,
|
|
EVENT_TYPE=EVENT_TYPE,
|
|
EVENT_TIME=EVENT_TIME,
|
|
PR_NUMBER=PR_NUMBER,
|
|
RUN_ID=RUN_ID,
|
|
CHANGE_URL=CHANGE_URL,
|
|
COMMIT_URL=COMMIT_URL,
|
|
RUN_URL=RUN_URL,
|
|
BASE_BRANCH=BASE_BRANCH,
|
|
INSTANCE_TYPE=INSTANCE_TYPE,
|
|
INSTANCE_ID=INSTANCE_ID,
|
|
PR_BODY=PR_BODY,
|
|
PR_TITLE=PR_TITLE,
|
|
USER_LOGIN=USER_LOGIN,
|
|
COMMIT_AUTHORS=COMMIT_AUTHORS,
|
|
FORK_NAME=FORK_NAME,
|
|
COMMIT_MESSAGE=COMMIT_MESSAGE,
|
|
PR_LABELS=PR_LABELS,
|
|
INSTANCE_LIFE_CYCLE=INSTANCE_LIFE_CYCLE,
|
|
REPORT_MESSAGES=[],
|
|
LINKED_PR_NUMBER=LINKED_PR_NUMBER,
|
|
# TODO: Find a better way to store and pass commit authors data through workflow
|
|
JOB_KV_DATA={
|
|
"commit_authors": COMMIT_AUTHORS,
|
|
# Initial parent PR inference:
|
|
# - Defaults to LINKED_PR_NUMBER, which is the PR merged by a push/merge-queue event
|
|
# - Can be explicitly overridden later via workflow hooks (see Info.set_parent_pr_number)
|
|
"parent_pr_number": LINKED_PR_NUMBER,
|
|
},
|
|
WORKFLOW_JOB_DATA=WORKFLOW_JOB_DATA,
|
|
WORKFLOW_CONFIG=None,
|
|
)
|
|
|
|
@classmethod
|
|
def file_name_static(cls, _name=""):
|
|
return f"{Settings.TEMP_DIR}/{cls.name}.json"
|
|
|
|
@classmethod
|
|
def from_dict(cls: Type[T], obj: Dict[str, Any]) -> T:
|
|
JOB_OUTPUT_STREAM = os.getenv("GITHUB_OUTPUT", "")
|
|
obj["JOB_OUTPUT_STREAM"] = JOB_OUTPUT_STREAM
|
|
if "PARAMETER" in obj:
|
|
obj["PARAMETER"] = _to_object(obj["PARAMETER"])
|
|
# Filter out unexpected arguments - only keep fields defined in the dataclass
|
|
valid_fields = {f.name for f in dataclasses.fields(cls)}
|
|
filtered_obj = {k: v for k, v in obj.items() if k in valid_fields}
|
|
return cls(**filtered_obj)
|
|
|
|
@classmethod
|
|
def from_workflow_data(cls) -> "_Environment":
|
|
with open(Settings.WORKFLOW_STATUS_FILE, "r", encoding="utf8") as f:
|
|
workflow_status_data = json.load(f)
|
|
# Access the config job data and parse the JSON string in "data" field
|
|
# Job names are normalized in the workflow status file
|
|
normalized_job_name = Utils.normalize_string(Settings.CI_CONFIG_JOB_NAME)
|
|
config_job_data = workflow_status_data.get(normalized_job_name, {})
|
|
data_str = config_job_data.get("outputs", {}).get("data", "")
|
|
# A suppressed output may surface either as a missing key or as a blank
|
|
# string - do not let json.loads choke on the latter before the
|
|
# explicit error below
|
|
if isinstance(data_str, str):
|
|
env_dict = json.loads(data_str) if data_str.strip() else None
|
|
else:
|
|
env_dict = data_str
|
|
if not env_dict or "SHA" not in env_dict:
|
|
raise RuntimeError(
|
|
f"Job output [data] of the [{Settings.CI_CONFIG_JOB_NAME}] job is empty or missing. "
|
|
"If that job itself succeeded, the runner has likely suppressed the output: "
|
|
"it refuses to export a job output that matches a secret pattern "
|
|
"(look for a [##[warning]Skip output 'data' since it may contain secret] line "
|
|
f"at the very end of the [{Settings.CI_CONFIG_JOB_NAME}] job log)"
|
|
)
|
|
|
|
# JOB_KV_DATA is stored as opaque base64 in the initial job's output
|
|
# (see Runner.run) to keep user-authored strings such as changed file
|
|
# paths from matching a secret pattern and suppressing the output -
|
|
# decode it back into a dict here
|
|
kv_data = env_dict.get("JOB_KV_DATA")
|
|
if isinstance(kv_data, str):
|
|
env_dict["JOB_KV_DATA"] = (
|
|
json.loads(Utils.from_base64(kv_data)) if kv_data else {}
|
|
)
|
|
|
|
# User-authored free text is stripped from the initial job's output
|
|
# (see Runner.run: the runner drops outputs matching secret patterns) -
|
|
# restore it from the local event payload
|
|
event_file_path = os.getenv("GITHUB_EVENT_PATH", "")
|
|
if event_file_path and Path(event_file_path).is_file():
|
|
with open(event_file_path, "r", encoding="utf-8") as f:
|
|
github_event = json.load(f)
|
|
if "pull_request" in github_event:
|
|
env_dict["PR_BODY"] = github_event["pull_request"]["body"] or ""
|
|
env_dict["PR_TITLE"] = github_event["pull_request"]["title"] or ""
|
|
elif github_event.get("head_commit"):
|
|
env_dict["COMMIT_MESSAGE"] = (
|
|
github_event["head_commit"]["message"] or ""
|
|
)
|
|
|
|
# Reread instance metadata from the host
|
|
env_dict["INSTANCE_TYPE"] = (
|
|
os.getenv("INSTANCE_TYPE", None)
|
|
or Shell.get_output("ec2metadata --instance-type")
|
|
or ""
|
|
)
|
|
env_dict["INSTANCE_ID"] = (
|
|
os.getenv("INSTANCE_ID", None)
|
|
or Shell.get_output("ec2metadata --instance-id")
|
|
or ""
|
|
)
|
|
env_dict["INSTANCE_LIFE_CYCLE"] = (
|
|
os.getenv("INSTANCE_LIFE_CYCLE", None)
|
|
or Shell.get_output(
|
|
"curl -s --fail http://169.254.169.254/latest/meta-data/instance-life-cycle"
|
|
)
|
|
or ""
|
|
)
|
|
|
|
# Override WORKFLOW_JOB_DATA with the current job's data so that
|
|
# check_run_id refers to this job rather than to the config job whose
|
|
# serialised environment we loaded above.
|
|
env_dict["WORKFLOW_JOB_DATA"] = cls._load_workflow_job_data()
|
|
|
|
return cls.from_dict(env_dict)
|
|
|
|
def _add_report_message(self, message, kind, source=""):
|
|
"""
|
|
Accumulate a structured report message in the environment.
|
|
|
|
Messages are collected during job execution and later written to both
|
|
the job and workflow ``Result.ext`` as ``{"message": str, "from": str}``
|
|
entries. Grouping of duplicate messages is done at the rendering level
|
|
in ``json.html``.
|
|
Prefer the typed wrappers ``add_workflow_warning/error/note``.
|
|
"""
|
|
self.REPORT_MESSAGES.append(
|
|
{"message": message, "kind": kind, "from": source or self.JOB_NAME}
|
|
)
|
|
self.dump()
|
|
|
|
def add_workflow_warning(self, message, source=""):
|
|
self._add_report_message(message, kind="warning", source=source)
|
|
|
|
def add_workflow_error(self, message, source=""):
|
|
self._add_report_message(message, kind="error", source=source)
|
|
|
|
def add_workflow_note(self, message, source=""):
|
|
self._add_report_message(message, kind="note", source=source)
|
|
|
|
@classmethod
|
|
def get(cls):
|
|
if Path(cls.file_name_static()).is_file():
|
|
return cls.from_fs("environment")
|
|
else:
|
|
try:
|
|
env = cls.from_workflow_data()
|
|
env.dump()
|
|
return env
|
|
except FileNotFoundError:
|
|
# For workflows without Config job
|
|
print(
|
|
f"NOTE: Workflow context file [{Settings.WORKFLOW_STATUS_FILE}] does not exist - read context from GH event"
|
|
)
|
|
env = cls.from_env()
|
|
env.dump()
|
|
return env
|
|
|
|
def set_job_name(self, job_name):
|
|
self.JOB_NAME = job_name
|
|
self.dump()
|
|
return self
|
|
|
|
def set_pr_labels(self, labels: List[str], reset: bool = False) -> None:
|
|
if reset:
|
|
self.PR_LABELS = list(labels)
|
|
else:
|
|
for label in labels:
|
|
if label not in self.PR_LABELS:
|
|
self.PR_LABELS.append(label)
|
|
self.dump()
|
|
|
|
def add_pr_label(self, label: str) -> None:
|
|
if label not in self.PR_LABELS:
|
|
self.PR_LABELS.append(label)
|
|
self.dump()
|
|
|
|
def remove_pr_label(self, label: str) -> None:
|
|
if label in self.PR_LABELS:
|
|
self.PR_LABELS.remove(label)
|
|
self.dump()
|
|
|
|
@staticmethod
|
|
def get_needs_statuses():
|
|
if Path(Settings.WORKFLOW_STATUS_FILE).is_file():
|
|
with open(Settings.WORKFLOW_STATUS_FILE, "r", encoding="utf8") as f:
|
|
return json.load(f)
|
|
else:
|
|
print(
|
|
f"ERROR: Status file [{Settings.WORKFLOW_STATUS_FILE}] does not exist"
|
|
)
|
|
raise RuntimeError()
|
|
|
|
def get_s3_prefix(self, latest=False):
|
|
return self.get_s3_prefix_static(self.PR_NUMBER, self.BRANCH, self.SHA, latest)
|
|
|
|
@classmethod
|
|
def get_s3_prefix_static(cls, pr_number, branch, sha, latest=False):
|
|
assert pr_number > 0 or branch
|
|
if pr_number:
|
|
prefix = f"PRs/{pr_number}"
|
|
else:
|
|
prefix = f"REFs/{branch}"
|
|
assert sha or latest
|
|
if latest:
|
|
prefix += "/latest"
|
|
elif sha:
|
|
prefix += f"/{sha}"
|
|
return prefix
|
|
|
|
def is_local_run(self):
|
|
return self.LOCAL_RUN
|
|
|
|
|
|
def _to_object(data):
|
|
if isinstance(data, dict):
|
|
return SimpleNamespace(**{k: _to_object(v) for k, v in data.items()})
|
|
elif isinstance(data, list):
|
|
return [_to_object(i) for i in data]
|
|
else:
|
|
return data
|