From patchwork Fri May 1 10:59:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2249 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633274; bh=yVnNaW9BgAT3V7tKtlylnUdHwP8314U6YYiQcdOn/xU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=HCtT0BgDZJO8kjzB0mZ17BExJYjmEEymVTMNPo2O/m6ja9hr8ORAYuHq4aeEpJej0 YpbppOJOrnKXWRP3fb5+dqVUieB8Y/auRrVg89wWZTiKq8X/TFiK8+ddRFuxomZQAj gwZzVhI+5fiZQyGdlWYPPWa0fqdQUefL5OJXtoos= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B72196A83B for ; Fri, 1 May 2026 05:01:14 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 5e-Dl1ZzD6BX for ; Fri, 1 May 2026 05:01:14 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633273; bh=yVnNaW9BgAT3V7tKtlylnUdHwP8314U6YYiQcdOn/xU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=kBfMGI0+atMf11VfjilUuRbf1RD5DrrAzm3I5RHjQ3VE7RLai8zoI13MTu8aS5V9w a5C/9dTiABtmRuuJFE0LAuwuPiidOAq+4g1MVqgx89X2mikqIZkTVNYNyUZjLlQVwE pISdwBev5DCmLa3QWGRq/YL+aLjSzuOmTq0E73m4= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B63FF6A834 for ; Fri, 1 May 2026 05:01:13 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633271; bh=kEXs3EccrvFlGTF+aj5p3Zj9lejY3JN1KDFhG2CH4P4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hz+a6zFw+7sqIsDRXRN6PS1F0AQJ1zRDPOLPdr299q4OArvcz+hvVibdPla73HmiA W+zTfPYquLpVJeNcvVbuMbP+0XmoHcqOeflqI8j3p1yiQKFrkKBAmXoZbY2djFddN+ FtZLxtCfpL36ZcufDLyBP2cqmlRmAbClYt46/o7k= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C7ADA6A82E; Fri, 1 May 2026 05:01:11 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id gglqyGxkv4dP; Fri, 1 May 2026 05:01:11 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1777633271; bh=clGqQxRZQnfao/6Arczm4x659A+xgNsiVG7239sNS3U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kB3+iCAZ3zjmWnYosSIY8A6CEi+yfVcoAq6SEp0R74MFxX2cAp60muBEt+NKZCyLm nxFP3TwQfmUA5XqAKEkKOaVbJQdmrhUJJOE1wV7nMVrpOLfTFz/k5ZEelIGgT9ezb5 EgcLLZTI90GC55aeJA+j5VVV1zrYGQEw7YkxHlWE= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 2FD466A7AF; Fri, 1 May 2026 05:01:11 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Fri, 1 May 2026 04:59:58 -0600 Message-ID: <20260501110040.1874719-7-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260501110040.1874719-1-sjg@u-boot.org> References: <20260501110040.1874719-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 2XXUBACQMWU3AQDORASOVHUQBZICKQR3 X-Message-ID-Hash: 2XXUBACQMWU3AQDORASOVHUQBZICKQR3 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 06/29] patman: Use patchwork.py for all HTTP access in review.py List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass review.py reaches into patchwork in two ways: via public Patchwork methods (get_series, get_cover_comments, etc.) and, in a few remaining places, by either constructing patchwork URLs itself or calling Patchwork._request() directly. The direct paths are: - fetch_mbox() builds 'series//mbox/' itself and downloads via aiohttp, so the apply_series chain takes 'pwork_url' (a bare string) rather than the Patchwork instance. - _do_learn_voice() pokes pwork._request(client, 'projects/') to find the list_email for the configured project. Add a get_series_mbox() method to Patchwork so the mbox URL is expressed once, and a search_patches() public wrapper around the existing 'patches/?project=...&q=...' subpath. Then switch review.py over: fetch_mbox(), apply_series(), and apply_series_sync() now take the Patchwork instance, _apply_and_check() passes ctx.pwork instead of ctx.pwork.url, and _do_learn_voice() uses pwork.get_projects(). review.py no longer constructs patchwork URLs or calls the private _request(). Signed-off-by: Simon Glass --- tools/patman/patchwork.py | 46 +++++++++++++++++++++++++++++++++++++++ tools/patman/review.py | 42 +++++++++++++++-------------------- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/tools/patman/patchwork.py b/tools/patman/patchwork.py index 5613cbb3383..46bacee50d2 100644 --- a/tools/patman/patchwork.py +++ b/tools/patman/patchwork.py @@ -449,6 +449,52 @@ class Patchwork: """ return await self._request(client, f'series/{link}/') + async def get_series_mbox(self, client, link): + """Download the raw mbox file for a series. + + The mbox URL lives directly under '/series//mbox/' rather + than the JSON API, so this fetches the bytes directly rather + than routing through self._request(). + + Args: + client (aiohttp.ClientSession): Session to use + link (str): Patchwork series link/ID + + Returns: + bytes: Raw mbox content + + Raises: + ValueError: if the download fails + """ + self.request_count += 1 + full_url = f'{self.url}/series/{link}/mbox/' + async with self.semaphore: + async with client.get(full_url) as response: + if response.status != 200: + raise ValueError( + f'Failed to download mbox: HTTP {response.status}') + return await response.read() + + async def search_patches(self, client, query, project_id=None, + per_page=20): + """Search patches by free-text query, most-recent first. + + Args: + client (aiohttp.ClientSession): Session to use + query (str): Text to match against patch titles + project_id (int): Project ID to scope to, or None to use + self.proj_id + per_page (int): Maximum number of results to return + + Returns: + list of dict: Patch records matching the query + """ + if project_id is None: + project_id = self.proj_id + subpath = (f'patches/?project={project_id}&q={query}' + f'&order=-date&per_page={per_page}') + return await self._request(client, subpath) + async def get_patch(self, client, patch_id): """Read information about a patch diff --git a/tools/patman/review.py b/tools/patman/review.py index 09614c85ed7..eb2e46a0431 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -100,11 +100,11 @@ class ReviewContext: # pylint: disable=R0902 return self.series_data.get('date', '') -async def fetch_mbox(pwork_url, link): +async def fetch_mbox(pwork, link): """Download the series mbox file from patchwork Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID Returns: @@ -113,19 +113,13 @@ async def fetch_mbox(pwork_url, link): Raises: ValueError: if the download fails """ - url = f'{pwork_url}/series/{link}/mbox/' - tout.notice(f'Downloading mbox from {url}') + tout.notice(f'Downloading mbox for series {link} from {pwork.url}') mbox_path = os.path.join(tempfile.gettempdir(), f'patman_review_{link}.mbox') async with aiohttp.ClientSession() as client: - async with client.get(url) as response: - if response.status != 200: - raise ValueError( - f'Failed to download mbox: HTTP {response.status}') - - content = await response.read() - if not content: - raise ValueError(f'Empty mbox downloaded from {url}') + content = await pwork.get_series_mbox(client, link) + if not content: + raise ValueError(f'Empty mbox downloaded for series {link}') tools.write_file(mbox_path, content) tout.notice(f'Downloaded {len(content)} bytes to {mbox_path}') @@ -205,7 +199,7 @@ IMPORTANT: ''' -async def apply_series(pwork_url, link, branch_name, upstream_branch, +async def apply_series(pwork, link, branch_name, upstream_branch, repo_path): """Download and apply a patch series to a new local branch @@ -213,7 +207,7 @@ async def apply_series(pwork_url, link, branch_name, upstream_branch, resolution. Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID branch_name (str): Name for the new branch upstream_branch (str): Branch to base from @@ -228,7 +222,7 @@ async def apply_series(pwork_url, link, branch_name, upstream_branch, return False, None # Download the mbox - mbox_path = await fetch_mbox(pwork_url, link) + mbox_path = await fetch_mbox(pwork, link) # Build the prompt and run the agent prompt = _build_apply_prompt(mbox_path, branch_name, upstream_branch) @@ -1058,11 +1052,11 @@ def review_patches_sync(ctx): return loop.run_until_complete(review_patches(ctx)) -def apply_series_sync(pwork_url, link, branch_name, upstream_branch, repo_path): +def apply_series_sync(pwork, link, branch_name, upstream_branch, repo_path): """Synchronous wrapper for apply_series() Args: - pwork_url (str): Patchwork server URL + pwork (Patchwork): Patchwork instance to fetch from link (str): Patchwork series link/ID branch_name (str): Name for the new branch upstream_branch (str): Branch to base from @@ -1073,7 +1067,7 @@ def apply_series_sync(pwork_url, link, branch_name, upstream_branch, repo_path): """ loop = asyncio.get_event_loop() return loop.run_until_complete(apply_series( - pwork_url, link, branch_name, upstream_branch, repo_path)) + pwork, link, branch_name, upstream_branch, repo_path)) def search_series(pwork, title): @@ -1273,12 +1267,10 @@ def _do_learn_voice(args, pwork): if pwork and pwork.proj_id: async def _get_list_email(): - async with aiohttp.ClientSession() as client: - # pylint: disable=W0212 - projects = await pwork._request(client, 'projects/') - for proj in projects: - if proj['id'] == pwork.proj_id: - return proj.get('list_email') + projects = await pwork.get_projects() + for proj in projects: + if proj['id'] == pwork.proj_id: + return proj.get('list_email') return None loop = asyncio.get_event_loop() @@ -1600,7 +1592,7 @@ def _apply_and_check(ctx, link): """ branch_name = f'review{ctx.series_id}' repo_path = gitutil.get_top_level() - success, branch_name = apply_series_sync(ctx.pwork.url, link, branch_name, + success, branch_name = apply_series_sync(ctx.pwork, link, branch_name, ctx.upstream_branch, repo_path) if success: