Skip to content

Commit 650481f

Browse files
authored
Merge branch 'main' into feat/event-queue-async-context-manager-720
2 parents cddf878 + 6ba923b commit 650481f

26 files changed

Lines changed: 2340 additions & 376 deletions
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
name: Post Coverage Comment
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Run Unit Tests"]
6+
types:
7+
- completed
8+
9+
permissions:
10+
pull-requests: write
11+
actions: read
12+
13+
jobs:
14+
comment:
15+
runs-on: ubuntu-latest
16+
if: >
17+
github.event.workflow_run.event == 'pull_request' &&
18+
github.event.workflow_run.conclusion == 'success'
19+
steps:
20+
- name: Download Coverage Artifacts
21+
uses: actions/download-artifact@v4
22+
with:
23+
run-id: ${{ github.event.workflow_run.id }}
24+
github-token: ${{ secrets.A2A_BOT_PAT }}
25+
name: coverage-data
26+
27+
- name: Upload Coverage Report
28+
id: upload-report
29+
uses: actions/upload-artifact@v4
30+
with:
31+
name: coverage-report
32+
path: coverage/
33+
retention-days: 14
34+
35+
- name: Post Comment
36+
uses: actions/github-script@v6
37+
env:
38+
ARTIFACT_URL: ${{ steps.upload-report.outputs.artifact-url }}
39+
with:
40+
script: |
41+
const fs = require('fs');
42+
43+
const { owner, repo } = context.repo;
44+
const headSha = context.payload.workflow_run.head_commit.id;
45+
46+
const loadSummary = (path) => {
47+
try {
48+
const data = JSON.parse(fs.readFileSync(path, 'utf8'));
49+
// Map Python coverage.json format to expected internal summary format
50+
if (data.totals && data.files) {
51+
const summary = {
52+
total: {
53+
statements: { pct: data.totals.percent_covered }
54+
}
55+
};
56+
for (const [file, fileData] of Object.entries(data.files)) {
57+
// Python coverage uses absolute paths or relative to project root
58+
// We keep it as is for comparison
59+
summary[file] = {
60+
statements: { pct: fileData.summary.percent_covered }
61+
};
62+
}
63+
return summary;
64+
}
65+
return data;
66+
} catch (e) {
67+
console.log(`Could not read ${path}: ${e}`);
68+
return null;
69+
}
70+
};
71+
72+
const baseSummary = loadSummary('./coverage-base.json');
73+
const prSummary = loadSummary('./coverage-pr.json');
74+
75+
if (!baseSummary || !prSummary) {
76+
console.log("Missing coverage data, skipping comment.");
77+
return;
78+
}
79+
80+
let baseBranch = 'main';
81+
try {
82+
baseBranch = fs.readFileSync('./BASE_BRANCH', 'utf8').trim();
83+
} catch (e) {
84+
console.log("Could not read BASE_BRANCH, defaulting to main.");
85+
}
86+
87+
let markdown = `### 🧪 Code Coverage (vs \`${baseBranch}\`)\n\n`;
88+
89+
markdown += `[⬇️ **Download Full Report**](${process.env.ARTIFACT_URL})\n\n`;
90+
91+
const metric = 'statements';
92+
const getPct = (summaryItem, m) => summaryItem && summaryItem[m] ? Number(summaryItem[m].pct) : 0;
93+
94+
const formatDiff = (oldPct, newPct) => {
95+
const diff = (newPct - oldPct).toFixed(2);
96+
97+
let icon = '';
98+
if (diff > 0) icon = '🟢';
99+
else if (diff < 0) icon = '🔴';
100+
else icon = '⚪️';
101+
102+
const diffStr = diff > 0 ? `+${diff}%` : `${diff}%`;
103+
return `${icon} ${diffStr}`;
104+
};
105+
106+
const fileUrl = (path) => `https://github.com/${owner}/${repo}/blob/${headSha}/${path}`;
107+
108+
const allFiles = new Set([...Object.keys(baseSummary), ...Object.keys(prSummary)]);
109+
allFiles.delete('total');
110+
const workspacePath = process.env.GITHUB_WORKSPACE ? process.env.GITHUB_WORKSPACE + '/' : '';
111+
112+
let changedRows = [];
113+
let newRows = [];
114+
115+
for (const file of allFiles) {
116+
const baseFile = baseSummary[file];
117+
const prFile = prSummary[file];
118+
119+
if (!prFile) continue;
120+
121+
const oldPct = getPct(baseFile, metric);
122+
const newPct = getPct(prFile, metric);
123+
124+
const relativeFilePath = file.replace(workspacePath, '');
125+
const linkedPath = `[${relativeFilePath}](${fileUrl(relativeFilePath)})`;
126+
127+
if (!baseFile && prFile) {
128+
newRows.push(`| ${linkedPath} (**new**) | — | ${newPct.toFixed(2)}% | — |\n`);
129+
} else if (oldPct !== newPct) {
130+
changedRows.push(`| ${linkedPath} | ${oldPct.toFixed(2)}% | ${newPct.toFixed(2)}% | ${formatDiff(oldPct, newPct)} |\n`);
131+
}
132+
}
133+
134+
if (changedRows.length === 0 && newRows.length === 0) {
135+
markdown += `\n_No coverage changes._\n`;
136+
} else {
137+
markdown += `| | Base | PR | Delta |\n`;
138+
markdown += `| :--- | :---: | :---: | :---: |\n`;
139+
140+
if (changedRows.length > 0) {
141+
markdown += changedRows.sort().join('');
142+
}
143+
if (newRows.length > 0) {
144+
markdown += newRows.sort().join('');
145+
}
146+
147+
const oldTotalPct = getPct(baseSummary.total, metric);
148+
const newTotalPct = getPct(prSummary.total, metric);
149+
if (oldTotalPct !== newTotalPct) {
150+
markdown += `| **Total** | ${oldTotalPct.toFixed(2)}% | ${newTotalPct.toFixed(2)}% | ${formatDiff(oldTotalPct, newTotalPct)} |\n`;
151+
}
152+
}
153+
154+
markdown += `\n\n_Generated by [coverage-comment.yml](https://github.com/${owner}/${repo}/actions/workflows/coverage-comment.yml)_`;
155+
156+
const prNumber = fs.readFileSync('./PR_NUMBER', 'utf8').trim();
157+
158+
if (!prNumber) {
159+
console.log("No PR number found.");
160+
return;
161+
}
162+
163+
const comments = await github.rest.issues.listComments({
164+
owner,
165+
repo,
166+
issue_number: prNumber,
167+
});
168+
169+
const existingComment = comments.data.find(c =>
170+
c.body.includes('Generated by [coverage-comment.yml]') &&
171+
c.user.type === 'Bot'
172+
);
173+
174+
if (existingComment) {
175+
await github.rest.issues.updateComment({
176+
owner,
177+
repo,
178+
comment_id: existingComment.id,
179+
body: markdown
180+
});
181+
} else {
182+
await github.rest.issues.createComment({
183+
owner,
184+
repo,
185+
issue_number: prNumber,
186+
body: markdown
187+
});
188+
}

.github/workflows/linter.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
- name: Run Pyright (Pylance equivalent)
4444
id: pyright
4545
continue-on-error: true
46-
uses: jakebailey/pyright-action@v2
46+
uses: jakebailey/pyright-action@v3
4747
with:
4848
pylance-version: latest-release
4949

.github/workflows/python-publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
run: uv build
2727

2828
- name: Upload distributions
29-
uses: actions/upload-artifact@v6
29+
uses: actions/upload-artifact@v7
3030
with:
3131
name: release-dists
3232
path: dist/
@@ -40,7 +40,7 @@ jobs:
4040

4141
steps:
4242
- name: Retrieve release distributions
43-
uses: actions/download-artifact@v7
43+
uses: actions/download-artifact@v8
4444
with:
4545
name: release-dists
4646
path: dist/

.github/workflows/unit-tests.yml

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
---
22
name: Run Unit Tests
33
on:
4-
pull_request:
4+
push:
55
branches: [main]
6+
pull_request:
67
permissions:
78
contents: read
9+
810
jobs:
911
test:
1012
name: Test with Python ${{ matrix.python-version }}
@@ -54,7 +56,65 @@ jobs:
5456
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
5557
- name: Install dependencies
5658
run: uv sync --locked
57-
- name: Run tests and check coverage
59+
60+
# Coverage comparison for PRs (only on Python 3.13 to avoid duplicate work)
61+
- name: Checkout Base Branch
62+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
63+
uses: actions/checkout@v4
64+
with:
65+
ref: ${{ github.event.pull_request.base.ref || 'main' }}
66+
clean: true
67+
68+
- name: Run coverage (Base)
69+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
70+
run: |
71+
uv run pytest --cov=a2a --cov-report=json --cov-report=html:coverage
72+
mv coverage.json /tmp/coverage-base.json
73+
74+
- name: Checkout PR Branch (Restore)
75+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
76+
uses: actions/checkout@v4
77+
with:
78+
clean: true
79+
80+
- name: Run coverage (PR)
81+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
82+
run: |
83+
uv run pytest --cov=a2a --cov-report=json --cov-report=html:coverage --cov-report=term --cov-fail-under=88
84+
mv coverage.json coverage-pr.json
85+
cp /tmp/coverage-base.json coverage-base.json
86+
87+
- name: Save Metadata
88+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
89+
run: |
90+
echo ${{ github.event.number }} > ./PR_NUMBER
91+
echo ${{ github.event.pull_request.base.ref || 'main' }} > ./BASE_BRANCH
92+
93+
- name: Upload Coverage Artifacts
94+
uses: actions/upload-artifact@v4
95+
if: github.event_name == 'pull_request' && matrix.python-version == '3.13'
96+
with:
97+
name: coverage-data
98+
path: |
99+
coverage-base.json
100+
coverage-pr.json
101+
coverage/
102+
PR_NUMBER
103+
BASE_BRANCH
104+
retention-days: 1
105+
106+
# Run standard tests (for matrix items that didn't run coverage PR)
107+
- name: Run tests (Standard)
108+
if: matrix.python-version != '3.13' || github.event_name != 'pull_request'
58109
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
110+
111+
- name: Upload Artifact (base)
112+
uses: actions/upload-artifact@v4
113+
if: github.event_name != 'pull_request' && matrix.python-version == '3.13'
114+
with:
115+
name: coverage-report
116+
path: coverage
117+
retention-days: 14
118+
59119
- name: Show coverage summary in log
60120
run: uv run coverage report

.pre-commit-config.yaml

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,6 @@ repos:
3131
# ===============================================
3232
# Python Hooks
3333
# ===============================================
34-
# no_implicit_optional for ensuring explicit Optional types
35-
- repo: https://github.com/hauntsaninja/no_implicit_optional
36-
rev: '1.4'
37-
hooks:
38-
- id: no_implicit_optional
39-
args: [--use-union-or]
40-
41-
# Pyupgrade for upgrading Python syntax to newer versions
42-
- repo: https://github.com/asottile/pyupgrade
43-
rev: v3.20.0
44-
hooks:
45-
- id: pyupgrade
46-
args: [--py310-plus] # Target Python 3.10+ syntax, matching project's target
47-
48-
# Autoflake for removing unused imports and variables
49-
- repo: https://github.com/pycqa/autoflake
50-
rev: v2.3.1
51-
hooks:
52-
- id: autoflake
53-
args: [--in-place, --remove-all-unused-imports]
54-
5534
# Ruff for linting and formatting
5635
- repo: https://github.com/astral-sh/ruff-pre-commit
5736
rev: v0.12.0

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [0.3.25](https://github.com/a2aproject/a2a-python/compare/v0.3.24...v0.3.25) (2026-03-10)
4+
5+
6+
### Features
7+
8+
* Implement a vertex based task store ([#752](https://github.com/a2aproject/a2a-python/issues/752)) ([fa14dbf](https://github.com/a2aproject/a2a-python/commit/fa14dbf46b603f288a1f1c474401483bf53950e4))
9+
10+
11+
### Bug Fixes
12+
13+
* return background task from consume_and_break_on_interrupt to prevent GC ([#775](https://github.com/a2aproject/a2a-python/issues/775)) ([a236d4d](https://github.com/a2aproject/a2a-python/commit/a236d4df8dceb2db1e1170e0b57599f3837ebd71))
14+
* use default_factory for mutable field defaults in ServerCallContext ([#744](https://github.com/a2aproject/a2a-python/issues/744)) ([22b25d6](https://github.com/a2aproject/a2a-python/commit/22b25d653e57e2d1453bbc282052e51dbd904ac6))
15+
316
## [0.3.24](https://github.com/a2aproject/a2a-python/compare/v0.3.23...v0.3.24) (2026-02-20)
417

518

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ postgresql = ["sqlalchemy[asyncio,postgresql-asyncpg]>=2.0.0"]
3737
mysql = ["sqlalchemy[asyncio,aiomysql]>=2.0.0"]
3838
signing = ["PyJWT>=2.0.0"]
3939
sqlite = ["sqlalchemy[asyncio,aiosqlite]>=2.0.0"]
40+
vertex = ["google-cloud-aiplatform>=1.140.0"]
4041

4142
sql = ["a2a-sdk[postgresql,mysql,sqlite]"]
4243

@@ -47,6 +48,7 @@ all = [
4748
"a2a-sdk[grpc]",
4849
"a2a-sdk[telemetry]",
4950
"a2a-sdk[signing]",
51+
"a2a-sdk[vertex]",
5052
]
5153

5254
[project.urls]
@@ -100,9 +102,6 @@ dev = [
100102
"types-protobuf",
101103
"types-requests",
102104
"pre-commit",
103-
"pyupgrade",
104-
"autoflake",
105-
"no_implicit_optional",
106105
"trio",
107106
"uvicorn>=0.35.0",
108107
"pytest-timeout>=2.4.0",

scripts/format.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ if [ -n "$CHANGED_FILES" ]; then
7575
echo "Files to be formatted:"
7676
echo "$CHANGED_FILES"
7777

78-
echo "Running autoflake..."
79-
run_formatter "$CHANGED_FILES" autoflake -i -r --remove-all-unused-imports
8078
echo "Running ruff check (fix-only)..."
8179
run_formatter "$CHANGED_FILES" ruff check --fix-only $RUFF_UNSAFE_FIXES_FLAG
8280
echo "Running ruff format..."

src/a2a/contrib/__init__.py

Whitespace-only changes.

src/a2a/contrib/tasks/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)