From patchwork Wed Jun 24 17:20:01 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90870 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8E999CDE000 for ; Wed, 24 Jun 2026 17:20:21 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12947.1782321619328716956 for ; Wed, 24 Jun 2026 10:20:19 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=mNoT2d/8; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxO2602576; Wed, 24 Jun 2026 10:20:18 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=L5KksOtieDECjFnMFcVVqxfMSodcL9Z/WUAEbqcyXfg=; b= mNoT2d/8XK2RaFksnhx3JrP4QzwjTGNEaSZ8k6f44+BhqRqWED1vEDhvFFQscwZt 1Chm6CeI9wddoObImbn78mw2V4+UM9148DXsUGtkYuj2caNKexq8H6FTOfDxSiK8 /ebpJYBCR/8Q6BcUoRfo7U/DLaeihf/N/fZGDk+I7ukeohAfwQh4TIHEF3Mvmxgm 1JZRMf6cFlfXOHP5qJbHaiDrw02g6lpzx+hfVC6s43c8KM9XMy4zEVu9y+iCtuHn KblsuJhR4cNuBeg2LmxHpkjAqJZDy/cqbr2X2feh0RM9IfgNr3OmTXLjTQ0NL/G+ Kc+c7EdcqrWKmo9ipkUG4g== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-2 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Wed, 24 Jun 2026 10:20:17 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:12 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:12 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:01 -0700 Subject: [PATCH v4 01/11] bin/*: Add/improve __version__ processing MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-1-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley , Richard Purdie X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX2jWXU1VIjDbD G7R6rRKjxjb7md0hOkhDuGNcdo9SuL5jrNCqcJ7AYsT7TcrnyuP0566UFwX1qYDE7L5g2flSBkF zNsDnS0XKt0jO4cyfyt8SHgYDbWlrYEYTN4WdKN1czfk69DCpSV4AlMRmlt39zFg/ZYBAbWcVt8 G6cAdHd+QP1XMcS2dclkaWDqCgjdd8TMpKXLiu2OrpwqrX5laPBn3Q5aScjoO9/BQ4Gdu8uClAh 52TMxM4IOmcgmdWrtVFNXYxuzsKUlf3d4UKH61VlxjkWe3D32DmIkRGo3ENWAujf7UVIdXjKbjg 4Tr0zO+PNJZRGNWqgll7CPshhH0b1OdDOM9cTLzIzp3J5nB7/Tpr6fLWy80Mg55r83gqaRYlgdt KcroN7Ltwjkzquyl45u4UJJX0SKAgg== X-Proofpoint-ORIG-GUID: K5RHXYEgTeYVS3Zrz3qJywOorcURoq68 X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX/oIw/96+tgf3 QzUN/6ebjW8CSdYeoZTxhHjSERMmtmGAXqeRgeUfOBMtXBYnN4btpzuZ7hLsGi698kqjHmo8Eu6 tIK1TCx2j408fSh4eRqUiTDLnZm/qd5sVKBlGPWZ/9dpzUkoSrPD X-Proofpoint-GUID: K5RHXYEgTeYVS3Zrz3qJywOorcURoq68 X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d1 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=ag1SF4gXAAAA:8 a=AZ9A8onwP7jMkqFZqIcA:9 a=QEXdDO2ut3YA:10 a=Yupwre4RP9_Eg_Bd0iYG:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19768 From: Richard Purdie Add __version__ entries to hashserv and prserv libraries, then update all the bin/* scripts to verify the library versions they get match themselves. We've avoided doing this for maintenance reasons but with the possibility of finding a pypi based bitbake and mismatching, we really need to do this now. A couple of existing version strings were replaced with a more standard format so it is easy to update. Signed-off-by: Richard Purdie --- bin/bitbake | 2 +- bin/bitbake-diffsigs | 7 +++++++ bin/bitbake-getvar | 6 ++++++ bin/bitbake-hashclient | 8 ++++++++ bin/bitbake-hashserv | 7 +++++-- bin/bitbake-layers | 6 ++++++ bin/bitbake-prserv | 8 ++++++-- bin/bitbake-selftest | 5 +++++ bin/bitbake-server | 6 ++++++ bin/bitbake-setup | 6 ++++++ bin/bitbake-worker | 6 ++++++ bin/toaster | 2 ++ bin/toaster-eventreplay | 7 +++++++ lib/bb/main.py | 2 +- lib/hashserv/__init__.py | 2 ++ lib/prserv/__init__.py | 2 +- 16 files changed, 75 insertions(+), 7 deletions(-) diff --git a/bin/bitbake b/bin/bitbake index fd5511c62..0f155158c 100755 --- a/bin/bitbake +++ b/bin/bitbake @@ -31,7 +31,7 @@ __version__ = "2.19.0" if __name__ == "__main__": if __version__ != bb.__version__: - sys.exit("Bitbake core version and program version mismatch!") + sys.exit("Bitbake core library version and program version mismatch!") try: sys.exit(bitbake_main(BitBakeConfigParameters(sys.argv), cookerdata.CookerConfiguration())) diff --git a/bin/bitbake-diffsigs b/bin/bitbake-diffsigs index 9d6cb8c94..25a8d3068 100755 --- a/bin/bitbake-diffsigs +++ b/bin/bitbake-diffsigs @@ -22,6 +22,13 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), ' import bb.tinfoil import bb.siggen import bb.msg +from bb import __version__ as libbb_version + +__version__ = "2.19.0" + +if __version__ != libbb_version: + sys.exit("Bitbake core library version and program version mismatch!") + myname = os.path.basename(sys.argv[0]) logger = bb.msg.logger_create(myname) diff --git a/bin/bitbake-getvar b/bin/bitbake-getvar index 378fb1357..0ee33cfba 100755 --- a/bin/bitbake-getvar +++ b/bin/bitbake-getvar @@ -19,8 +19,14 @@ sys.path[0:0] = [os.path.join(topdir, 'lib')] import bb.providers import bb.tinfoil +from bb import __version__ as libbb_version + +__version__ = "2.19.0" if __name__ == "__main__": + if __version__ != libbb_version: + sys.exit("Bitbake core library version and program version mismatch!") + parser = argparse.ArgumentParser(description="Bitbake Query Variable") parser.add_argument("variable", help="variable name to query") parser.add_argument("-r", "--recipe", help="Recipe name to query", default=None, required=False) diff --git a/bin/bitbake-hashclient b/bin/bitbake-hashclient index b8755c579..3a2bf5c0d 100755 --- a/bin/bitbake-hashclient +++ b/bin/bitbake-hashclient @@ -20,6 +20,8 @@ import statistics import textwrap warnings.simplefilter("default") +__version__ = "2.19.0" + try: import tqdm ProgressBar = tqdm.tqdm @@ -41,6 +43,12 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib import hashserv import bb.asyncrpc +from bb import __version__ as libbb_version + +if __version__ != libbb_version: + sys.exit("Bitbake core library version and program version mismatch!") +if __version__ != hashserv.__version__: + sys.exit("Bitbake hashserv library version and program version mismatch!") DEFAULT_ADDRESS = 'unix://./hashserve.sock' METHOD = 'stress.test.method' diff --git a/bin/bitbake-hashserv b/bin/bitbake-hashserv index 01503736b..4818584e9 100755 --- a/bin/bitbake-hashserv +++ b/bin/bitbake-hashserv @@ -12,6 +12,8 @@ import argparse import sqlite3 import warnings +__version__ = "2.19.0" + warnings.simplefilter("default") sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")) @@ -19,14 +21,15 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib import hashserv from hashserv.server import DEFAULT_ANON_PERMS -VERSION = "1.0.0" +if __version__ != hashserv.__version__: + sys.exit("Bitbake library hashserv version and program version mismatch!") DEFAULT_BIND = "unix://./hashserve.sock" def main(): parser = argparse.ArgumentParser( - description="Hash Equivalence Reference Server. Version=%s" % VERSION, + description="Hash Equivalence Reference Server. Version=%s" % __version__, formatter_class=argparse.RawTextHelpFormatter, epilog=""" The bind address may take one of the following formats: diff --git a/bin/bitbake-layers b/bin/bitbake-layers index c49a5f30e..afbf50666 100755 --- a/bin/bitbake-layers +++ b/bin/bitbake-layers @@ -17,6 +17,8 @@ import argparse import warnings warnings.simplefilter("default") +__version__ = "2.19.0" + bindir = os.path.dirname(__file__) toolname = os.path.basename(__file__).split(".")[0] topdir = os.path.dirname(bindir) @@ -25,6 +27,10 @@ sys.path[0:0] = [os.path.join(topdir, 'lib')] import bb.tinfoil import bb.cooker import bb.msg +from bb import __version__ as libbb_version + +if __version__ != libbb_version: + sys.exit("Bitbake library version and program version mismatch!") logger = bb.msg.logger_create(toolname, sys.stdout) diff --git a/bin/bitbake-prserv b/bin/bitbake-prserv index 3992e84ea..d46c38516 100755 --- a/bin/bitbake-prserv +++ b/bin/bitbake-prserv @@ -11,12 +11,16 @@ import argparse import warnings warnings.simplefilter("default") +__version__ = "2.19.0" + sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")) import prserv import prserv.serv -VERSION = "2.0.0" +if __name__ == "__main__": + if __version__ != prserv.__version__: + sys.exit("Bitbake prserv library version and program version mismatch!") PRHOST_DEFAULT="0.0.0.0" PRPORT_DEFAULT=8585 @@ -30,7 +34,7 @@ def init_logger(logfile, loglevel): def main(): parser = argparse.ArgumentParser( - description="BitBake PR Server. Version=%s" % VERSION, + description="BitBake PR Server. Version=%s" % __version__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument( diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest index fb7c57dd8..7f6a64438 100755 --- a/bin/bitbake-selftest +++ b/bin/bitbake-selftest @@ -11,6 +11,8 @@ import warnings warnings.simplefilter("default") sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) +__version__ = "2.19.0" + import unittest try: import bb @@ -20,6 +22,9 @@ try: except RuntimeError as exc: sys.exit(str(exc)) +if __version__ != bb.__version__: + sys.exit("Bitbake core library version and program version mismatch!") + tests = ["bb.tests.codeparser", "bb.tests.color", "bb.tests.cooker", diff --git a/bin/bitbake-server b/bin/bitbake-server index 1428f72a7..6860876b7 100755 --- a/bin/bitbake-server +++ b/bin/bitbake-server @@ -12,6 +12,8 @@ warnings.simplefilter("default") import logging sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib')) +__version__ = "2.19.0" + import bb bb.utils.check_system_locale() @@ -23,6 +25,10 @@ if len(sys.argv) != 11 or not sys.argv[1].startswith("decafbad"): import bb.server.process +if __name__ == "__main__": + if __version__ != bb.__version__: + sys.exit("Bitbake core library version and program version mismatch!") + lockfd = int(sys.argv[2]) readypipeinfd = int(sys.argv[3]) logfile = sys.argv[4] diff --git a/bin/bitbake-setup b/bin/bitbake-setup index 664bffee3..d1e40c9a6 100755 --- a/bin/bitbake-setup +++ b/bin/bitbake-setup @@ -21,12 +21,18 @@ import sys import textwrap import time +__version__ = "2.19.0" bindir = os.path.abspath(os.path.dirname(__file__)) sys.path[0:0] = [os.path.join(os.path.dirname(bindir), 'lib')] import bb.msg # noqa: E402 import bb.process # noqa: E402 +from bb import __version__ as libbb_version + +if __name__ == "__main__": + if __version__ != libbb_version: + sys.exit("Bitbake core library version and program version mismatch!") logger = bb.msg.logger_create('bitbake-setup', sys.stdout) diff --git a/bin/bitbake-worker b/bin/bitbake-worker index aa14ef191..400737126 100755 --- a/bin/bitbake-worker +++ b/bin/bitbake-worker @@ -25,6 +25,12 @@ import subprocess import fcntl from threading import Thread +__version__ = "2.19.0" + +if __name__ == "__main__": + if __version__ != bb.__version__: + sys.exit("Bitbake core library version and program version mismatch!") + Lock = bb.multiprocessing.Lock # Remove when we have a minimum of python 3.10 diff --git a/bin/toaster b/bin/toaster index 16a64bab6..51bad203d 100755 --- a/bin/toaster +++ b/bin/toaster @@ -7,6 +7,8 @@ # SPDX-License-Identifier: GPL-2.0-or-later # +__version__ = "2.19.0" + HELP=" Usage 1: source toaster start|stop [webport=] [noweb] [nobuild] [toasterdir] Optional arguments: diff --git a/bin/toaster-eventreplay b/bin/toaster-eventreplay index 74a319320..30767f956 100755 --- a/bin/toaster-eventreplay +++ b/bin/toaster-eventreplay @@ -31,6 +31,13 @@ sys.path.insert(0, join(dirname(dirname(abspath(__file__))), 'lib')) import bb.cooker from bb.ui import toasterui from bb.ui import eventreplay +from bb import __version__ as libbb_version + +__version__ = "2.19.0" + +if __name__ == "__main__": + if __version__ != libbb_version: + sys.exit("Bitbake core library version and program version mismatch!") def main(argv): with open(argv[-1]) as eventfile: diff --git a/lib/bb/main.py b/lib/bb/main.py index 597cb2784..73a66468f 100755 --- a/lib/bb/main.py +++ b/lib/bb/main.py @@ -316,7 +316,7 @@ class BitBakeConfigParameters(cookerdata.ConfigParameters): options = parser.parse_intermixed_args(argv[1:]) if options.version: - print("BitBake Build Tool Core version %s" % bb.__version__) + print("BitBake Build Tool version %s" % bb.__version__) sys.exit(0) if options.quiet and options.verbose: diff --git a/lib/hashserv/__init__.py b/lib/hashserv/__init__.py index ba8e0acce..39bb29da5 100644 --- a/lib/hashserv/__init__.py +++ b/lib/hashserv/__init__.py @@ -12,6 +12,8 @@ from collections import namedtuple from urllib.parse import urlparse from bb.asyncrpc.client import parse_address, ADDR_TYPE_UNIX, ADDR_TYPE_WS +__version__ = "2.19.0" + User = namedtuple("User", ("username", "permissions")) UNIHASH_REGEX = re.compile(r"^[0-9a-f]{64}$") diff --git a/lib/prserv/__init__.py b/lib/prserv/__init__.py index ffc5a40a2..b30249b13 100644 --- a/lib/prserv/__init__.py +++ b/lib/prserv/__init__.py @@ -5,7 +5,7 @@ # -__version__ = "2.0.0" +__version__ = "2.19.0" import logging logger = logging.getLogger("BitBake.PRserv") From patchwork Wed Jun 24 17:20:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90869 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 73AB5CDB47F for ; Wed, 24 Jun 2026 17:20:21 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12948.1782321619828816596 for ; Wed, 24 Jun 2026 10:20:19 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=ONRRi8TZ; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxP2602576 for ; Wed, 24 Jun 2026 10:20:19 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=fzqI1Joi8JMgLtZZ9GBSYGAa+UMlYZx3lRUHkL0YYj8=; b= ONRRi8TZ/r5fbsRiVruNMdHsnBjM48WQZC6WE5vUI2tFYrjMu7SVdME+sVf+n8eY h57n+9HN0V8DVk6/PBtQF2fU0eLK2qQtqcNGijy22imNW5WMxb1Kz8Emj6NCDc9S ht3Ld8PeHO1pAzT0MsUKLGAiZ8zxb3ajIPhCP9L1Mzp3MGgyBNJbo/r1Q0NfTnqr 7eH+xr9oNtTiQuridwRNBqftm2VejdLLwBLQi1QE6GnUezORMGw+s5bA89PFdTp7 J+mqfweqI0FnFVxSAlZTxJf36JIaMQtGdfekFW6oSeZJoTlDvoTDsZ70xYrwFRrk gbth0VzQKdOhrwARksJOMw== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-3 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:18 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:12 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:12 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:02 -0700 Subject: [PATCH v4 02/11] bitbake-setup: Add version option MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-2-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXxeqHVBHpm82y 2sxTVvuSz/rBIH7/q8xvhUj0reBIoFDMifbqAlUyxE0QDNHd+1eAroHOLouZIkIg6DAq2zWYNu6 T7vMcaoVbZR+viGk5f4Io1nXtuypFfH8epbXLE96BH/M0X+Lb0MvkQKTaIodn4As0hqaBlJxMCc HwujnOUTDm1BuyZmR/ue+ZtSwLelP4ICa66hMKoa47iWtriMnbZDboJFuu71NPsMu6EcxhrFfwW 1knhPgt7zV/FPpJ45DPYNg0a48rZleEaVRNkLNmuzzXMZMN0MtPeM42rNL4FnB3uv1lLx6g6wk8 bwv6odMW1bJw98nlqlEF/RwamsTP0/u0uCF4kWOnPTGYj5WieZ+Y53Vq3qjICsClUH2KrECCNzy VaAmOwlCTdNsWLcOvlJmtmQpuVxaYA== X-Proofpoint-ORIG-GUID: AgIQxOJRi2_H2MdB3puqgGvrpH1TiFkW X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX8WDIep6seEjX BVA7YJlAaIkEsxIKxF27ySRlc30CLku1ZfMVrf5jVAPTuot2ZUHb6Az77N2X9VrYqJu5p+153Ui 2FwGgMtUS/7okZ/HZnf1fUkkv9VWkOiqUVJ3vD+2RlKcBBGf2dqF X-Proofpoint-GUID: AgIQxOJRi2_H2MdB3puqgGvrpH1TiFkW X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d2 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=3MZ2cW2_6ngPcrpQ0mEA:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19769 This adds the long-form version option to match bitbake. Signed-off-by: Rob Woolley --- bin/bitbake-setup | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/bitbake-setup b/bin/bitbake-setup index d1e40c9a6..525187ddf 100755 --- a/bin/bitbake-setup +++ b/bin/bitbake-setup @@ -1250,6 +1250,7 @@ def main(): ) parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') parser.add_argument('-q', '--quiet', help='Print only errors', action='store_true') + parser.add_argument('--version', action='version', version='%(prog)s ' + __version__) parser.add_argument('--color', choices=['auto', 'always', 'never'], default='auto', help='Colorize output (where %(metavar)s is %(choices)s)', metavar='COLOR') parser.add_argument('--no-network', action='store_true', help='Do not check whether configuration repositories and layer repositories have been updated; use only the local cache.') parser.add_argument('--global-settings', action='store', metavar='PATH', help='Path to the global settings file.') From patchwork Wed Jun 24 17:20:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90871 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CFA09CDE008 for ; Wed, 24 Jun 2026 17:20:21 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.12745.1782321621082561537 for ; Wed, 24 Jun 2026 10:20:21 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=TdW69jbj; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxQ2602576 for ; Wed, 24 Jun 2026 10:20:20 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=zECQ8D4kNuhUHa6lAsYMcjNr77VRztSZYV9Q+i5kvdw=; b= TdW69jbjzVxRjXS5eNS8FINM5UTQzCsjX/Mg/Iq7XIMVf2BGQPj693FazoRKvScx b05j71n/cn8EMY4V941+FZi+i73QcDgerZWwgYfUjpPYjX2SkxkFoL4iweOi0POX svExDc5qf+0bv2lA71ljTb7JZJLkilGfXDxyKQi/eHpuCOc7cljE4f126bd/z9rV 97R7aN8sLFlsPzcnGgme3FqvnrbkSn5BJeLLBBC6uyMlh3oWY6Qn/ROYHlvm5Wj+ wBMSBsyHYsLSfqNig7/9nIOdekLJssbRWlbiQLn0EgPcrNxjJFtaYsaCoUhplK9+ hw00Yo8jiGyKDfhicxLKjg== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-4 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:19 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:12 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:12 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:03 -0700 Subject: [PATCH v4 03/11] pypi: Add PyPI packaging for bitbake-setup MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-3-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXxAcF81zLWUTH 5HFVnyQz/d9xgTzorYgZD8x3sePH1jcbhiukt48kUKqm04MHozPvBAtwI1zA2zdTynWZM0pyPVp YBDDJaDL9663TBbxObmd28uHIdn42nvLFt6rCYPTi9Yi5/WwtVJ4vwCQ/tJl2Ds+u6iuvC4NVN6 p/DQT+1KsP/icNXnNwKP/xt5JpDRd+x+jEmFvOdK2pyzGf862f0WbYMurTEC6CQGzO/B/nhmDhO SEnF1+EhX5+YcObnKDej09KmCEs4NZ+yOyHRoJ0cj+FmsVJd24cd7CPSmID+wlQCQ0GsfLhmYRf OfrhEvHniesOb+sZbGEGkYZdLTambhbGlyntCKL/HHfuVxcpTxzHA0rE3ldtTscNQ+ouMwETFpt NItPSSMzThpgcGzauO0HkqwtSFN7gQ== X-Proofpoint-ORIG-GUID: 24r2Mw7Zi78KeIA8l2HC4mFRAWsfTccS X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX6obn2iNiGyVG evHwtnDAcyyF1ERYvgejA2ch/600REcnNsaHwpm0F3QKVSdKXxQ96x86+0VTLgg6NJo6xP+g7xz xXE3ERqI/XkFrA7lBF+rMeIs6FWGM2x22YWK7f2OWpuqG8EYv0Od X-Proofpoint-GUID: 24r2Mw7Zi78KeIA8l2HC4mFRAWsfTccS X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d3 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=24AZYWMyAAAA:8 a=iGHA9ds3AAAA:8 a=Q4-j1AaZAAAA:8 a=t7CeM3EgAAAA:8 a=qV09NasGAAAA:8 a=V5MqrhjLj9XmrIPkEmEA:9 a=QEXdDO2ut3YA:10 a=bG88sKzkDEFeXWNnvthB:22 a=nM-MV4yxpKKO9kiQg6Ot:22 a=9H3Qd4_ONW2Ztcrla5EB:22 a=FdTzh2GWekK77mhwV6Dw:22 a=GlicbclHOgpI_Rq0ze_Y:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:21 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19770 We wish to publish bitbake-setup to PyPI to improve the workflow for new users that are familiar with pip install. In order to publish bitbake-setup as a standalone package, we must create a staging directory for Python to build the package. The package-bitbake-setup.py automates the staging of the necessary files. You may supply your desired directory as the first argument, but packaging_workspace in the root of the git repository is chosen by default. The packaging process and related tests have also been included as part of bitbake-selftest and lib/bb/tests/setup.py. These tests use package-bitbake-setup.py. The tests include: * Verify bitbake-setup --help runs successfully. * Verify bitbake-setup list runs successfully. * Verify console script entry points are correctly defined. * Verify all expected modules can be imported from installed package. * Verify package metadata is correctly set. * Verify vendored dependencies (bs4, ply, progressbar, simplediff) are not bundled in package. * Verify version is set correctly (not fallback 0.0.0 unless expected). * Verify wheel METADATA file contains required fields. The pyproject.toml, LICENSE, and README.md files are used to create the package and provide information for PyPI. Assisted-by: Claude:claude-4.6-opus Signed-off-by: Rob Woolley --- bin/bitbake-selftest | 2 + contrib/pypi/LICENSE | 23 +++ contrib/pypi/README.md | 42 +++++ contrib/pypi/package-bitbake-setup.py | 79 ++++++++++ contrib/pypi/pyproject.toml | 92 +++++++++++ lib/bb/tests/setup.py | 289 +++++++++++++++++++++++++++++++++- 6 files changed, 526 insertions(+), 1 deletion(-) diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest index 7f6a64438..8ac0aeb5f 100755 --- a/bin/bitbake-selftest +++ b/bin/bitbake-selftest @@ -72,6 +72,8 @@ ENV_HELP = """\ Environment variables: BB_SKIP_NETTESTS set to 'yes' in order to skip tests using network connection + BB_SKIP_PYPI_TESTS set to 'no' to run PyPI packaging tests + (default: yes/skip) BB_TMPDIR_NOCLEAN set to 'yes' to preserve test tmp directories """ diff --git a/contrib/pypi/LICENSE b/contrib/pypi/LICENSE new file mode 100644 index 000000000..4b4c73672 --- /dev/null +++ b/contrib/pypi/LICENSE @@ -0,0 +1,23 @@ +bitbake-setup is licensed under the GNU General Public License version 2.0. See +LICENSE.GPL-2.0-only for further details. + +Individual files contain the following style tags instead of the full license text: + + SPDX-License-Identifier: GPL-2.0-only + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ + +The bitbake-setup package vendorizes the following modules: + +beautifulsoup4 + SPDX-License-Identifier: MIT + +typing_extensions + SPDX-License-Identifier: PSF-2.0 + +progressbar: + SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear + +simplediff: + SPDX-License-Identifier: Zlib diff --git a/contrib/pypi/README.md b/contrib/pypi/README.md new file mode 100644 index 000000000..aaa702783 --- /dev/null +++ b/contrib/pypi/README.md @@ -0,0 +1,42 @@ +# bitbake-setup + +This package provides the `bitbake-setup` command and the Python modules +required to support BitBake setup and configuration. + +## Usage + +Instructions on uses of bitbake-setup can be found in +[Setting Up the Environment With bitbake-setup](https://docs.yoctoproject.org/bitbake/dev/bitbake-user-manual/bitbake-user-manual-environment-setup.html) from the Yocto Project manual. + +List the available configurations; +```bash +bitbake-setup list +``` + +Show the help for the bitbake-setup init subcommand: +```bash +bitbake-setup init --help +usage: bitbake-setup init [-h] [--non-interactive] [--source-overrides SOURCE_OVERRIDES] [--setup-dir-name SETUP_DIR_NAME] [--skip-selection SKIP_SELECTION] [-L SOURCE_NAME PATH] + [config ...] + +positional arguments: + config path/URL/id to a configuration file (use 'list' command to get available ids), followed by configuration options. Bitbake-setup will ask to choose from available + choices if command line doesn't completely specify them. + +options: + -h, --help show this help message and exit + --non-interactive Do not ask to interactively choose from available options; if bitbake-setup cannot make a decision it will stop with a failure. + --source-overrides SOURCE_OVERRIDES + Override sources information (repositories/revisions) with values from a local json file. + --setup-dir-name SETUP_DIR_NAME + A custom setup directory name under the top directory. + --skip-selection SKIP_SELECTION + Do not select and set an option/fragment from available choices; the resulting bitbake configuration may be incomplete. + -L SOURCE_NAME PATH, --use-local-source SOURCE_NAME PATH + Symlink local source into a build, instead of getting it as prescribed by a configuration (useful for local development). +``` + +To initialize a workspace for the Poky reference distro using the development branch (ie. "master") of OpenEmbedded: +```bash +bitbake-setup init poky-master +``` \ No newline at end of file diff --git a/contrib/pypi/package-bitbake-setup.py b/contrib/pypi/package-bitbake-setup.py new file mode 100755 index 000000000..60c503c4e --- /dev/null +++ b/contrib/pypi/package-bitbake-setup.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import shutil +from pathlib import Path + + +def create_packaging_workspace(directory): + # Create the directory for packaging workspace + if len(directory or "") > 0: + workspace_dir = Path(directory) + else: + # This script is located in contrib/pypi/package-bitbake-setup.py + workspace_dir = Path(__file__).parents[2] / "packaging_workspace" + + if not workspace_dir.exists(): + logging.debug(f"Created packaging workspace at: {workspace_dir}") + workspace_dir.mkdir(exist_ok=True) + else: + logging.debug(f"Packaging workspace already exists at: {workspace_dir}") + + # Copy packaging files to the workspace + files_to_copy = [ + "contrib/pypi/pyproject.toml", + "contrib/pypi/README.md", + "contrib/pypi/LICENSE", + "LICENSE.MIT", + "LICENSE.GPL-2.0-only" + ] + + for file in files_to_copy: + src_path = Path(__file__).parents[2] / file + dest_path = workspace_dir / Path(file).name + + if src_path.is_dir(): + shutil.copytree(src_path, dest_path, dirs_exist_ok=True) + logging.debug(f"Copied directory: {src_path} to {dest_path}") + else: + shutil.copy2(src_path, dest_path) + logging.debug(f"Copied file: {src_path} to {dest_path}") + + + # Copy necessary modules to the workspace + modules_to_bundle = [ + "lib/bb", + ] + + for module in modules_to_bundle: + src_path = Path(__file__).parents[2] / module + dest_path = workspace_dir / "src" / Path(module).name + + if src_path.is_dir(): + shutil.copytree(src_path, dest_path, dirs_exist_ok=True) + logging.debug(f"Bundled module directory: {src_path} to {dest_path}") + else: + shutil.copy2(src_path, dest_path) + logging.debug(f"Bundled module file: {src_path} to {dest_path}") + + # Create bitbake_setup module + bitbake_setup_dir = Path(workspace_dir / "src" / "bitbake_setup") + bitbake_setup_dir.mkdir(exist_ok=True) + Path(bitbake_setup_dir / "__init__.py").touch() + shutil.copy2(Path(__file__).parents[2] / "bin" / "bitbake-setup", str(bitbake_setup_dir / "__main__.py")) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + parser = argparse.ArgumentParser(description='Package bitbake-setup for PyPI') + parser.add_argument('-v', '--verbose', action='store_true', help='increase output verbosity.') + parser.add_argument('-d', '--directory', type=str, help='specify the directory to create the packaging workspace in.') + + args = parser.parse_args() + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + create_packaging_workspace(args.directory) diff --git a/contrib/pypi/pyproject.toml b/contrib/pypi/pyproject.toml new file mode 100644 index 000000000..90ed1ffb9 --- /dev/null +++ b/contrib/pypi/pyproject.toml @@ -0,0 +1,92 @@ +[build-system] +requires = [ + "setuptools>=64", +] +build-backend = "setuptools.build_meta" + +[project] +name = "bitbake-setup" +dynamic = ["version"] +description = "bitbake-setup" +readme = "README.md" +requires-python = ">=3.9" +license = "GPL-2.0-only AND MIT AND PSF-2.0 AND Zlib AND LGPL-2.1-or-later OR BSD-3-Clause-Clear" +authors = [ + { name = "OpenEmbedded BitBake Developers", email = "bitbake-devel@lists.openembedded.org" } +] +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: POSIX :: Linux", +] + +dependencies = [ + # bitbake-setup is mostly self-contained +] + +[project.optional-dependencies] +test = [ + "pytest>=7", +] + +lint = [ + "ruff>=0.3", + "mypy>=1.8", +] + +dev = [ + "pytest>=7", + "ruff>=0.3", + "mypy>=1.8", + "build", + "twine", +] + +[project.scripts] +bitbake-setup = "bitbake_setup.__main__:main" + +[project.urls] +Homepage = "https://git.openembedded.org/bitbake/" +Documentation = "https://docs.yoctoproject.org/bitbake/bitbake-user-manual/bitbake-user-manual-environment-setup.html" +Repository = "https://git.openembedded.org/bitbake/" + +[tool.mypy] +python_version = "3.9" +warn_unused_configs = true +ignore_missing_imports = true +no_implicit_optional = true +check_untyped_defs = false + +[tool.ruff] +target-version = "py39" +line-length = 88 +src = ["src"] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # pyflakes + "B", # flake8-bugbear + "I", # import sorting +] +ignore = [ + "E501", # line length (handled by formatter) +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +[tool.setuptools] +package-dir = { "" = "src" } +zip-safe = false +include-package-data = true + +[tool.setuptools.dynamic] +version = {attr = "bb.__version__"} + +[tool.setuptools.packages.find] +where = ["src"] +include = [ + "bb*", + "bitbake_setup" +] diff --git a/lib/bb/tests/setup.py b/lib/bb/tests/setup.py index 5592e8196..18039b72e 100644 --- a/lib/bb/tests/setup.py +++ b/lib/bb/tests/setup.py @@ -11,7 +11,14 @@ import glob import hashlib import json import os +import shutil import stat +import subprocess +import sys +import tempfile +import unittest +import venv +import zipfile from bb.tests.support.httpserver import HTTPService class BitbakeSetupTest(FetcherTest): @@ -92,7 +99,11 @@ print("BBPATH is {{}}".format(os.environ["BBPATH"])) def runbbsetup(self, cmd): bbsetup = os.path.abspath(os.path.dirname(__file__) + "/../../../bin/bitbake-setup") - return bb.process.run([bbsetup, '--global-settings', os.path.join(self.tempdir, 'global-config')] + cmd) + # Set PYTHONPATH so subprocess can find bb module instead of relying on the current directory + env = os.environ.copy() + libdir = os.path.abspath(os.path.dirname(__file__) + "/../..") + env["PYTHONPATH"] = libdir + ":" + env.get("PYTHONPATH", "") + return bb.process.run([bbsetup, '--global-settings', os.path.join(self.tempdir, 'global-config')] + cmd, env=env, cwd=self.tempdir) def _add_json_config_to_registry_helper(self, name, sources): @@ -735,3 +746,279 @@ print("BBPATH is {{}}".format(os.environ["BBPATH"])) self.assertEqual(f.read(), 'conflicting-upstream\n', "re-cloned layer must contain the upstream content after conflict backup") del os.environ['BBPATH'] + +@unittest.skipIf(os.environ.get("BB_SKIP_PYPI_TESTS", "yes") != "no", + "PyPI packaging test (set BB_SKIP_PYPI_TESTS=no to run)") +class PyPIPackagingTest(unittest.TestCase): + """Tests for PyPI packaging of bitbake-setup. + + These tests build a wheel from source, install it in an isolated venv, + and verify the package works correctly. Skipped by default unless + BB_SKIP_PYPI_TESTS=no is set. + """ + + wheel_path = None + build_tempdir = None + workspace_dir = None + + @classmethod + def setUpClass(cls): + """Build wheel once for all tests in this class.""" + # Locate contrib/pypi directory + cls.pypi_dir = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'contrib', 'pypi') + ) + + # Check for required build tools + cls._check_build_tools() + + # Create temporary directory for packaging workspace + cls.build_tempdir = tempfile.mkdtemp(prefix="bitbake-pypi-build-") + + # Run package-bitbake-setup.py to create packaging workspace + cls._create_packaging_workspace() + + # Build the wheel + cls._build_wheel() + + @classmethod + def _check_build_tools(cls): + """Verify build tools are available, skip if not.""" + try: + result = subprocess.run( + [sys.executable, "-m", "build", "--version"], + capture_output=True, check=True + ) + except (subprocess.CalledProcessError, FileNotFoundError): + raise unittest.SkipTest("'build' package not installed (pip install build)") + + @classmethod + def _create_packaging_workspace(cls): + """Create packaging workspace using package-bitbake-setup.py.""" + script = os.path.join(cls.pypi_dir, 'package-bitbake-setup.py') + cls.workspace_dir = os.path.join(cls.build_tempdir, 'workspace') + result = subprocess.run( + [sys.executable, script, '-d', cls.workspace_dir], + capture_output=True, text=True + ) + if result.returncode != 0: + raise unittest.SkipTest(f"Packaging workspace creation failed: {result.stderr}") + + @classmethod + def _build_wheel(cls): + """Build the wheel in the packaging workspace.""" + result = subprocess.run( + [sys.executable, "-m", "build", "--wheel"], + cwd=cls.workspace_dir, + capture_output=True, text=True + ) + if result.returncode != 0: + raise unittest.SkipTest(f"Wheel build failed: {result.stderr}") + + # Find the built wheel + dist_dir = os.path.join(cls.workspace_dir, 'dist') + wheels = glob.glob(os.path.join(dist_dir, '*.whl')) + if not wheels: + raise unittest.SkipTest("No wheel file found after build") + cls.wheel_path = wheels[0] + + @classmethod + def tearDownClass(cls): + """Clean up the shared wheel build artifacts.""" + if cls.build_tempdir and os.environ.get("BB_TMPDIR_NOCLEAN") != "yes": + shutil.rmtree(cls.build_tempdir, ignore_errors=True) + elif cls.build_tempdir: + print(f"Not cleaning up {cls.build_tempdir}. Please remove manually.") + + def setUp(self): + """Create isolated venv for testing the installed package.""" + self.venv_dir = tempfile.mkdtemp(prefix="bitbake-pypi-venv-") + + # Create venv without pip (faster, no network needed) + venv.create(self.venv_dir, with_pip=False, symlinks=True) + + # Get paths to venv python and bin directory + if sys.platform == 'win32': + self.venv_python = os.path.join(self.venv_dir, 'Scripts', 'python.exe') + self.venv_bin = os.path.join(self.venv_dir, 'Scripts') + else: + self.venv_python = os.path.join(self.venv_dir, 'bin', 'python') + self.venv_bin = os.path.join(self.venv_dir, 'bin') + + # Install wheel using pip from the outer environment (offline, no-deps) + site_packages = self._get_site_packages() + result = subprocess.run( + [sys.executable, "-m", "pip", "install", + "--target", site_packages, + "--no-deps", "--no-index", + self.wheel_path], + capture_output=True, text=True + ) + if result.returncode != 0: + self.fail(f"Failed to install wheel: {result.stderr}") + + # Install console script entry point manually + self._install_console_script() + + def _get_site_packages(self): + """Get the site-packages directory in the venv.""" + lib_dir = os.path.join(self.venv_dir, 'lib') + # Find python version directory + for d in os.listdir(lib_dir): + if d.startswith('python'): + return os.path.join(lib_dir, d, 'site-packages') + raise RuntimeError("Could not find site-packages in venv") + + def _install_console_script(self): + """Create console script wrapper in venv bin directory.""" + site_packages = self._get_site_packages() + script_path = os.path.join(self.venv_bin, 'bitbake-setup') + script_content = f'''#!{self.venv_python} +import sys +sys.path.insert(0, "{site_packages}") +from bitbake_setup.__main__ import main +sys.exit(main()) +''' + with open(script_path, 'w') as f: + f.write(script_content) + os.chmod(script_path, 0o755) + + def tearDown(self): + """Remove venv after test.""" + if os.environ.get("BB_TMPDIR_NOCLEAN") != "yes": + shutil.rmtree(self.venv_dir, ignore_errors=True) + else: + print(f"Not cleaning up {self.venv_dir}. Please remove manually.") + + def test_imports(self): + """Verify all expected modules can be imported from installed package.""" + + site_packages = self._get_site_packages() + imports = ['bb', 'bitbake_setup'] + for module in imports: + result = subprocess.run( + [self.venv_python, '-c', f'import sys; sys.path.insert(0, "{site_packages}"); import {module}'], + capture_output=True, text=True + ) + self.assertEqual(result.returncode, 0, + f"Failed to import {module}: {result.stderr}") + + def test_console_script_help(self): + """Verify bitbake-setup --help runs successfully.""" + script = os.path.join(self.venv_bin, 'bitbake-setup') + result = subprocess.run( + [script, '--help'], + capture_output=True, text=True + ) + self.assertEqual(result.returncode, 0, + f"bitbake-setup --help failed: {result.stderr}") + self.assertIn('usage:', result.stdout.lower()) + + def test_console_script_list(self): + """Verify bitbake-setup list runs successfully.""" + script = os.path.join(self.venv_bin, 'bitbake-setup') + result = subprocess.run( + [script, 'list'], + capture_output=True, text=True + ) + # List may return 0 even with no configurations + self.assertEqual(result.returncode, 0, + f"bitbake-setup list failed: {result.stderr}") + + def test_package_metadata(self): + """Verify package metadata is correctly set.""" + site_packages = self._get_site_packages() + code = ''' +import json +import sys +sys.path.insert(0, "{}") +from importlib.metadata import metadata +m = metadata("bitbake-setup") +print(json.dumps({{ + "name": m["Name"], + "version": m["Version"], + "requires_python": m.get("Requires-Python", ""), + "license": m.get("License", ""), +}})) +'''.format(site_packages) + result = subprocess.run( + [self.venv_python, '-c', code], + capture_output=True, text=True + ) + self.assertEqual(result.returncode, 0, + f"Failed to get metadata: {result.stderr}") + + meta = json.loads(result.stdout) + self.assertEqual(meta['name'], 'bitbake-setup') + self.assertIn('>=3.9', meta['requires_python']) + + def test_vendored_dependencies(self): + """Verify vendored dependencies (bs4, ply, progressbar, simplediff) are not bundled in package.""" + # Check that vendored packages do not exist in root of wheel + with zipfile.ZipFile(self.wheel_path, 'r') as whl: + names = whl.namelist() + + # Check for expected package directories + expected = ['bs4/', 'ply/', 'progressbar/', 'simplediff/'] + for pkg in expected: + found = any(n.startswith(pkg) for n in names) + self.assertFalse(found, + f"Unexpected vendored package '{pkg}' found in wheel") + + def test_version_from_wheel(self): + """Verify version is set correctly (not fallback 0.0.0 unless expected).""" + import re + # Extract version from wheel filename + wheel_name = os.path.basename(self.wheel_path) + # Wheel format: {name}-{version}(-{build})?-{python}-{abi}-{platform}.whl + parts = wheel_name.split('-') + version = parts[1] + + # Check version format (should be semver-like or contain git info) + version_pattern = r'^\d+\.\d+\.\d+.*$' + self.assertTrue(re.match(version_pattern, version), + f"Version '{version}' doesn't match expected pattern") + + print(f"Extracted version from wheel: {version}") + + self.assertNotEqual(version, '0.0.0', + "Version is fallback 0.0.0 - no git tags found") + + def test_wheel_metadata_file(self): + """Verify wheel METADATA file contains required fields.""" + with zipfile.ZipFile(self.wheel_path, 'r') as whl: + # Find METADATA file in dist-info + metadata_path = None + for name in whl.namelist(): + if name.endswith('.dist-info/METADATA'): + metadata_path = name + break + + self.assertIsNotNone(metadata_path, "METADATA file not found in wheel") + + # Parse metadata + metadata_content = whl.read(metadata_path).decode('utf-8') + + # Check required fields + self.assertIn('Metadata-Version:', metadata_content) + self.assertIn('Name: bitbake-setup', metadata_content) + self.assertIn('Version:', metadata_content) + self.assertIn('Requires-Python:', metadata_content) + + def test_entry_points(self): + """Verify console script entry points are correctly defined.""" + with zipfile.ZipFile(self.wheel_path, 'r') as whl: + # Find entry_points.txt in dist-info + entry_points_path = None + for name in whl.namelist(): + if name.endswith('.dist-info/entry_points.txt'): + entry_points_path = name + break + + self.assertIsNotNone(entry_points_path, + "entry_points.txt not found in wheel") + + content = whl.read(entry_points_path).decode('utf-8') + self.assertIn('[console_scripts]', content) + self.assertIn('bitbake-setup', content) + self.assertIn('bitbake_setup.__main__:main', content) From patchwork Wed Jun 24 17:20:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90875 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3152ACDE007 for ; Wed, 24 Jun 2026 17:20:32 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12949.1782321622298720958 for ; Wed, 24 Jun 2026 10:20:22 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=qh/MNyh/; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxR2602576 for ; Wed, 24 Jun 2026 10:20:22 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=imcY5tMdpJQljCCp+n4HfWFsCR0ft6OFKVT9qMI6Opo=; b= qh/MNyh/F4Ciz8q1oukXsJPxEemnFEaaxzBAwzeNRpFsTBE++WFVcs1rM3S/bS45 L9ZYm9N/OJxs60+Xuz4vdwbr7hKCQfkMnfNHe+xvH3Pu0cs8+T8Q2/E4s9aYlDOB Df4JECA1y0C30vvAsp++hK6QDklninUJdujSb6dVFR+XeDlYZTGum6uOQHJO6ObK XNYJRItNoVQtKgVmj6TpsfOAp+i/hBpz+9MxuF0nPs/WzuMytFNCgCZS80gxctGm KHlYtqWiZQdb7x+1X63iZ/vDghhsIOhfjRhleRvUtkJElXZNd3kWmnTu75YQ2giK 4q7sql6yIjLSr+DDVorO4Q== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-5 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:20 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:12 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:12 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:04 -0700 Subject: [PATCH v4 04/11] pypi: Add packaging documentation for developers MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-4-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX8PNc8QT9ylYK OBA6hxNgscMRvEK5hsC7t8sUy6anfSLqSMhyLWThkcdaRODxzZsznwFAFSqdIQZGEJZ1lrjRyFd OKSxJWjh2DcS2JwYE2QcOwrJd7ZXOKoTAXFQIjDB3pCyQUNHPhRJ0dPvGpzcv65hPliYZtDK+DM e26he6C6btYZvTKvT6+oChcSDD6k8NpnFbKhT62w+UEUmfpgoE6XRIFmzx4634UrmYun/AV9avK 1vaFvZcHCmK3e1F1fNCQQTh2EhkgFihtMhFt2TLDGDxAXc2geL7CecqLNTVAt1PIj5Zn1zVHATh BlNwowXHrCUpSOG/9AzekKLkWWPlx+S8TmRTaGFGSMYC/oXVWFtULV7lRLugPDoG/B7ro6WRWKH YS6/hOos7K9is6WMCnxqddXD/b90Bg== X-Proofpoint-ORIG-GUID: CPEG6bDcxZmTTqKzb5Duulq6q3oBDF2K X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX1Hz7kjLPhJSp ++zkrl+CFb1P1FLF3yMVrDTmynjnSkymUJmw3KlfojY7HDDyZsOH/EEIR4b7XgvPh/fnDXXDVBx kyoRDi0vpkDVkYfYYeYU3KE6S8qb2+rn+48QbM0ZdLEUpuT32XFN X-Proofpoint-GUID: CPEG6bDcxZmTTqKzb5Duulq6q3oBDF2K X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d4 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=SIW4pjiE_UkdO4QYDIoA:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:32 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19771 Signed-off-by: Rob Woolley --- contrib/pypi/BUILD.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/contrib/pypi/BUILD.md b/contrib/pypi/BUILD.md new file mode 100644 index 000000000..35a125ccd --- /dev/null +++ b/contrib/pypi/BUILD.md @@ -0,0 +1,50 @@ +# Development Instructions + +## Requirements + +- Python >= 3.9 +- pip >= 19 (for installation) + +## Testing + +To lint the `bitbake-setup` pypi packaging, run the ruff tool. +```bash +ruff check bin/bitbake-setup contrib/pypi +``` + +The steps to build and test the `bitbake-setup` pypi packaging have been automated with the `bitbake-selftest` tool. This tool automatically creates a Python virtual environment for you. + +Run the bitbake-selftest +```bash +BB_SKIP_PYPI_TESTS=no bin/bitbake-selftest -v bb.tests.setup.PyPIPackagingTest +``` + +## Packaging + +### Create the development sandbox + +To create the development sandbox run: +```bash +contrib/pypi/package-bitbake-setup.py +cd packaging_workspace +``` + +### Building the package + +To install the development tools manually run: +```bash +python3 -m pip install -e '.[dev]' +``` + +To build a wheel (.whl) then use: +```bash +python3 -m build +``` + +This produces a wheel (.whl) file in the dist directory. This may be installed using pip. + +### Installing the package + +```bash +python3 -m pip install dist/bitbake_setup-*-py3-none-any.whl +``` From patchwork Wed Jun 24 17:20:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90874 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 476C5CDE008 for ; Wed, 24 Jun 2026 17:20:32 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.12746.1782321623492099853 for ; Wed, 24 Jun 2026 10:20:23 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=lQ9HrlR4; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxS2602576 for ; Wed, 24 Jun 2026 10:20:23 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=Dpm+W8rbb+hyIH5Vgsq7bK45VNPwAivdAzTDuAru60s=; b= lQ9HrlR4w+M7SC/mCNJlVT+9FdY/UfK4eB+fcT4nBoBVBTYLo9m402P+mibBp/ZD T0lx8nu3mMP9WQku9KlPdLhDbCTz2mgV9JOX6PaTrGrmQFaThxPt26YgdOmN3nWF yAwQD9Zu0ZhKYkq2oBIXiBi2oiItT/+x+p7i9hmWOehw2/nErY9iRhil1PGhpSEJ UYbFygIS9+a6sfinq+uPnAzPnxde+HjFl54QikiJ7vZa7SKTpF5npMylmGDRS+WO WsY1rNM8btu96ASwytIYmUikGuko692sDe1xYDX+Q5+SggDjt8RtdacBWqFPuMq/ g4o38hKj+k/d0yXSK/ijXg== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-6 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:22 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:12 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:12 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:05 -0700 Subject: [PATCH v4 05/11] gitignore: Ignore temporary staging directory MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-5-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXzV3H7xN9abKj 6K+u4qssDKO0AdkuQsIxMYYAJ8Rffa9xVXYme5Xz7YdMnS0HxY/QuI8SayH7NFIohL8cF9CxSo+ +5BIZgBU7RSk4NOoMZptqazzf+iS43hgBVr6i2NvDbr0r+wLGSz6Y0Dpt84CN0eh45LHs6TvGSk 7rw94I4XCHIa5zRAR6o/8prf2Ayo6Z6mQ3eFbvuD9Ybcdyv8HL5pno4CB3UobpIahPM7BtLxkrh 3X1MpSq3eNigcu6soTF3lM/vdJi/2O2a9348iaA4eHLVyoONPU7qlPFHJ+ZPPhP2qmBLU2rM4n6 wQ9CyyU+hC3czB4E4kkkPBYJ47d5XphhPRhPbALQLXsRcSaavAJOgSAMdFMNFwOqIBB88TWkgJg CtLERQlKsC+iQSOzHOV+qU5l/ehDXQ== X-Proofpoint-ORIG-GUID: 3MuEA7INCaXWjcDNTJRrS0FbSYpdUVrO X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXySloqpIAA4IO OUBeXrw78EiZ5eTMDYFMPAAPD1LoCEB3qU0FVSUUzx7pEMDkRKyrObksw8Zop2Oj03q0HdMO1W0 0w7rPTpG6I0WwD7FTQEmDk+Ckba/2V0La2mbCr+tvJ60kvRZ6Kya X-Proofpoint-GUID: 3MuEA7INCaXWjcDNTJRrS0FbSYpdUVrO X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d6 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=0vOpnHimbWeZp4raT20A:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:32 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19772 This directory gets created automatically by package-bitbake-setup.py when packaging bitbake-setup for PyPI. It is a temporary staging directory and should not be added to git. Signed-off-by: Rob Woolley --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a6a256b2c..9a5c4ec49 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ lib/toaster/contrib/tts/backlog.txt lib/toaster/contrib/tts/log/* lib/toaster/contrib/tts/.cache/* lib/bb/tests/runqueue-tests/bitbake-cookerdaemon.log +packaging_workspace/ From patchwork Wed Jun 24 17:20:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90872 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 09DA2CDE002 for ; Wed, 24 Jun 2026 17:20:32 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.12749.1782321624764431472 for ; Wed, 24 Jun 2026 10:20:24 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=L0xtAR6Y; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxT2602576 for ; Wed, 24 Jun 2026 10:20:24 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=oDv9X9weBwQUpIx/I9WQodEEmFSBZ+IFbGQOmzBlkWQ=; b= L0xtAR6Y6tWFCrOEzlZAPozUFgzOVzPGPcR4w+gREuvIGDFKP4UL/4whNihwdW1l UTlwys3w+70TFyEEljBMkCW9VOCcqcEGE686w6jU9GpQaCA7b4P2ThpJwW6uXxW3 XXfArW1ykhGNCNhDwB/iaLnebG801CrLjgLlVx+KkDej4GOD63+H3ptutrIgAhD9 CDvWM0TDyxxdoY6pFGuqoaV4wRwyEYgmobAATe7mwkXZvVKEWXFtaQn/qYY3T6VP +suWVtEaecaXAuzb9z0aWGASaGi0Yy5es3V9JA0u04mzMissEuIk98Fnupr9IPCW Si1DFap+OsjwOB2cZuSlQQ== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-7 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:23 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:13 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:13 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:06 -0700 Subject: [PATCH v4 06/11] Add pyproject.toml and vendor.txt for vendoring MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-6-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX8d+NrTtUFi5M uvkSwXxaMJpe/07Lsv9zLmlmzrxmGkM6+ljFHL7Rg+/5JDmg1dXcwVveCzyuBCQIncoCYwHlF7K 913Hat6KoKvnmYv89Y3D5zA9PnAUz4jh5LdujbIOq9C9UfAetZOAyPpjtFro9KeE7sK5B3P1rx9 PalXjPkhGSRpu22WR5t8arRe+IcU+D6wRAYmUZ2MdwOYILTz4WUv8oMm2iSuxm6EzXGG9YoI1Ja oiRBOTJleRBW0c0OfxGeEyCMtW+tB7g04kY2yGvvlEivmSILokjyca7WDLaZ6TJIQAJlbcO2JQs ebwpRTmVf06tZeNXixPRRRYbL1dL8oJOOzVEMqA1cLcNMEP+2P1DThxVcVO5jws/H/MkBjI7G6Z 4dr/GkOV+KIwL7+WmKnjn1n8Impl0Q== X-Proofpoint-ORIG-GUID: pA7mLl6ZN2p9rTx6DAgajlgV7wSKjQZ0 X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX6bznFcP/UHBV w7dVeqyFCQiVpcidsfe77tcqIVWJplDJ+82TOE1IKvGxA/jebvz1emxLh9ElhtX8GCynBB6robQ xjBwHTzGOeuXCKpHnbgLzliAprUb0cn/U90TGMRRc36MZKbvHO/u X-Proofpoint-GUID: pA7mLl6ZN2p9rTx6DAgajlgV7wSKjQZ0 X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d7 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=-SJoDt46AAAA:20 a=t7CeM3EgAAAA:8 a=adcO8tCN0-mKZzeWj3YA:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 a=bA3UWDv6hWIuX7UZL3qL:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:31 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19773 Used for updateing the vendorized modules with: vendoring sync . Signed-off-by: Rob Woolley --- pyproject.toml | 19 +++++++++++++++++++ vendor.txt | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..93508d92e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = [] +build-backend = "setuptools.build_meta" + +[project] +name = "bitbake" +version = "2.19.0" + +[tool.vendoring] +destination = "lib/bb/_vendor" +requirements = "vendor.txt" +namespace = "bb._vendor" +patches-dir = "vendor/patches" + +[tool.vendoring.license.directories] +ply = "vendor/licenses/ply/LICENSE" + +[tool.vendoring.license.fallback-urls] +ply = "https://raw.githubusercontent.com/dabeaz/ply/refs/heads/master/src/ply/lex.py" diff --git a/vendor.txt b/vendor.txt new file mode 100644 index 000000000..c60d43092 --- /dev/null +++ b/vendor.txt @@ -0,0 +1,4 @@ +beautifulsoup4==4.15.0 +ply==3.10 +progressbar==2.5 +simplediff==1.1 From patchwork Wed Jun 24 17:20:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90876 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1D995CDB47F for ; Wed, 24 Jun 2026 17:20:32 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12954.1782321627052891583 for ; Wed, 24 Jun 2026 10:20:27 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@windriver.com header.s=PPS06212021 header.b=fUVLo3KO; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxU2602576 for ; Wed, 24 Jun 2026 10:20:26 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=MQZiVDF1XLRfUO5t2msZCSo88P7A0w0GSONiaJpE8+k=; b= fUVLo3KO8Q8/a5X864yZXC2W/3o6+LtEuv6J+SKdH6ZpTkwIz2PZLSHKBRZkjZ1X ktTafoNMqSWJ1faHqDtCb3ZaZZlxMTQVs85RLbPI4mnNmp+WpNqThSSHKJy3NeVu KYgn7VrwrWlXeW/qY0rfswTiJN+qtAlNoSEBvt8YJnWSS5q3cKXMPauGS1MH6gnX nllbm8Tiv7+Sg12BYGEKStNTVXJOD5SxPZkg1bXt/4Qtc/zuXNuaiZw6C2/Em7c7 9Yeica5fqfNTa0CcQxGxwyjMPwBi9qI+fx2ywGpqflwse2UH6fKnEk0eBX1fjSc5 5dGeLtBjDZlGyWyPPMOz1Q== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-8 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:24 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:13 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:13 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:07 -0700 Subject: [PATCH v4 07/11] Add vendor patches MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-7-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXxscItJ+3/J43 GSwEcSSA190WXnSbVoLXIG8YQbLDyXUfrEwXh4BICY+Ob/un7eDur7Lrx+HVPDDHsMhnHj33Td3 Tio9mRXuelzfZFZR2/F452PRN+XI2q4VwryuqVZtvxklDGS+dCv56iycZIftiUBao2kaDoP718K tMc7LJj7tjptfGk07USY4AOC5m+k8Hi4cqznKSTTX9wTH/+mb3iozTkdIrcz/oMYbfw7cmDKATw 57HIFzwr0K+/i9y0bSosDDTbs9Njs0EXpVI2FnDktzPCavZuGfYASFmrebCy+qcq3EsOWNPcFzb TE2sQxtSnKpFdCD6/f9a7TTr+zzEcMBEHBV3fNG/YTq97RIfVUKC8e84cb/K9lOHCQKTnswiTXb pr34zGnlgxC+ekF1bNzwsgGd/5RxUA== X-Proofpoint-ORIG-GUID: SJX0aggBNtTvXESfTOBs0-M37hebV11- X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXx5PiynzNHhyz 8QRJ1TWouIhhxMSjl+zc+UCfA/yDhJgY3IPpOH8WusMJDJ1/h1f469JYqYWFxTRuZsbxmIUTX4H ix4VoWsYllOWe7SInZm8VwwBFBVOu248nwV1TdEbwinbJJLXzk+H X-Proofpoint-GUID: SJX0aggBNtTvXESfTOBs0-M37hebV11- X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11d8 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=ag1SF4gXAAAA:8 a=QyXUC8HyAAAA:8 a=aRxQL1JOAAAA:8 a=P-IC7800AAAA:8 a=3-RhneuVAAAA:8 a=uvxgbxF22WCvZ4Xt2W0A:9 a=3ZKOabzyN94A:10 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 a=Yupwre4RP9_Eg_Bd0iYG:22 a=lBRdisTmIr2YKkuu8atg:22 a=d3PnA9EDa4IxuAV0gXij:22 a=VLVLkjT_5ZicWzSuYqSo:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 X-MIME-Autoconverted: from 8bit to quoted-printable by mx0a-0064b401.pphosted.com id 65OEegxU2602576 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:32 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19774 Signed-off-by: Rob Woolley --- vendor/licenses/ply/LICENSE | 32 ++++++ .../bs4-0010-lib-bs4-Avoid-soupsieve-warning.patch | 40 +++++++ vendor/patches/bs4-remove-double-imports.patch | 23 ++++ ...lib-implement-basic-task-progress-support.patch | 126 +++++++++++++++++++++ ...-initial-pass-of-SPDX-license-headers-to-.patch | 80 +++++++++++++ ...gressbar-accept-value-over-initial-maxval.patch | 55 +++++++++ ...-Add-self._fd_console-to-use-for-self._ha.patch | 54 +++++++++ ...ar-Make-bars-show-correctly-with-maxval-0.patch | 32 ++++++ ...-knotty-Allow-mixing-log-messages-and-pro.patch | 32 ++++++ 9 files changed, 474 insertions(+) diff --git a/vendor/licenses/ply/LICENSE b/vendor/licenses/ply/LICENSE new file mode 100644 index 000000000..d050b2ef9 --- /dev/null +++ b/vendor/licenses/ply/LICENSE @@ -0,0 +1,32 @@ +# ----------------------------------------------------------------------------- +# ply +# +# Copyright (C) 2001-2018 +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- diff --git a/vendor/patches/bs4-0010-lib-bs4-Avoid-soupsieve-warning.patch b/vendor/patches/bs4-0010-lib-bs4-Avoid-soupsieve-warning.patch new file mode 100644 index 000000000..ef0f1cc6b --- /dev/null +++ b/vendor/patches/bs4-0010-lib-bs4-Avoid-soupsieve-warning.patch @@ -0,0 +1,40 @@ +From 8e444cd9913d1ee0672b5583e263e5927c3221df Mon Sep 17 00:00:00 2001 +From: Richard Purdie +Date: Fri, 31 May 2024 13:09:44 +0100 +Subject: [PATCH 10/11] lib/bs4: Avoid soupsieve warning + +Signed-off-by: Richard Purdie +--- + lib/bs4/css.py | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/lib/bb/_vendor/bs4/css.py b/lib/bb/_vendor/bs4/css.py +index 245ac6010..cd1fd2df8 100644 +--- a/lib/bb/_vendor/bs4/css.py ++++ b/lib/bb/_vendor/bs4/css.py +@@ -24,23 +24,14 @@ + Optional, + TYPE_CHECKING, + ) +-import warnings + from bs4._typing import _NamespaceMapping + + if TYPE_CHECKING: +- from soupsieve import SoupSieve + from bs4 import element + from bs4.element import ResultSet, Tag + +-soupsieve: Optional[ModuleType] +-try: +- import soupsieve +-except ImportError: +- soupsieve = None +- warnings.warn( +- "The soupsieve package is not installed. CSS selectors cannot be used." +- ) +- ++# We don't use soupsieve ++soupsieve = None + + class CSS(object): + """A proxy object against the ``soupsieve`` library, to simplify its diff --git a/vendor/patches/bs4-remove-double-imports.patch b/vendor/patches/bs4-remove-double-imports.patch new file mode 100644 index 000000000..f7be7d029 --- /dev/null +++ b/vendor/patches/bs4-remove-double-imports.patch @@ -0,0 +1,23 @@ +commit 741db6719efca5aa9ef2c15e60cdd624e4aa1a8d +Author: Michael Estner +Date: Tue Feb 25 19:34:25 2025 +0100 + + lib: Remove double imports + + * Remove double imports mentioned by pylint + + Signed-off-by: Michael Estner + Signed-off-by: Richard Purdie + +diff --git a/lib/bb/_vendor/bs4/__init__.py b/lib/bb/_vendor/bs4/__init__.py +index d8ad5e1dc..725203d94 100644 +--- a/lib/bb/_vendor/bs4/__init__.py ++++ b/lib/bb/_vendor/bs4/__init__.py +@@ -1173,7 +1173,5 @@ + + # If this file is run as a script, act as an HTML pretty-printer. + if __name__ == "__main__": +- import sys +- + soup = BeautifulSoup(sys.stdin) + print((soup.prettify())) diff --git a/vendor/patches/progressbar-0001-lib-implement-basic-task-progress-support.patch b/vendor/patches/progressbar-0001-lib-implement-basic-task-progress-support.patch new file mode 100644 index 000000000..2f91b4b53 --- /dev/null +++ b/vendor/patches/progressbar-0001-lib-implement-basic-task-progress-support.patch @@ -0,0 +1,126 @@ +From 0d275fc5b6531957a6189069b04074065bb718a0 Mon Sep 17 00:00:00 2001 +From: Paul Eggleton +Date: Thu, 23 Jun 2016 22:59:05 +1200 +Subject: [PATCH 1/6] lib: implement basic task progress support + +For long-running tasks where we have some output from the task that +gives us some idea of the progress of the task (such as a percentage +complete), provide the means to scrape the output for that progress +information and show it to the user in the default knotty terminal +output in the form of a progress bar. This is implemented using a new +TaskProgress event as well as some code we can insert to do output +scanning/filtering. + +Any task can fire TaskProgress events; however, if you have a shell task +whose output you wish to scan for progress information, you just need to +set the "progress" varflag on the task. This can be set to: + * "percent" to just look for a number followed by a % sign + * "percent:" to specify your own regex matching a percentage + value (must have a single group which matches the percentage number) + * "outof:" to look for the specified regex matching x out of y + items completed (must have two groups - first group needs to be x, + second y). +We can potentially extend this in future but this should be a good +start. + +Part of the implementation for [YOCTO #5383]. + +Signed-off-by: Paul Eggleton +Signed-off-by: Richard Purdie +--- + lib/progressbar/progressbar.py | 16 +++++++++++---- + lib/progressbar/widgets.py | 36 ++++++++++++++++++++++++++++++++++ + 2 files changed, 48 insertions(+), 4 deletions(-) + +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index 0b9dcf763..2873ad6ca 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -3,6 +3,8 @@ + # progressbar - Text progress bar library for Python. + # Copyright (c) 2005 Nilton Volpato + # ++# (With some small changes after importing into BitBake) ++# + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public + # License as published by the Free Software Foundation; either +@@ -261,12 +263,14 @@ class ProgressBar(object): + now = time.time() + self.seconds_elapsed = now - self.start_time + self.next_update = self.currval + self.update_interval +- self.fd.write(self._format_line() + '\r') ++ output = self._format_line() ++ self.fd.write(output + '\r') + self.fd.flush() + self.last_update_time = now ++ return output + + +- def start(self): ++ def start(self, update=True): + """Starts measuring time, and prints the bar at 0%. + + It returns self so you can use it like this: +@@ -289,8 +293,12 @@ class ProgressBar(object): + self.update_interval = self.maxval / self.num_intervals + + +- self.start_time = self.last_update_time = time.time() +- self.update(0) ++ self.start_time = time.time() ++ if update: ++ self.last_update_time = self.start_time ++ self.update(0) ++ else: ++ self.last_update_time = 0 + + return self + +diff --git a/lib/bb/_vendor/progressbar/widgets.py b/lib/bb/_vendor/progressbar/widgets.py +index 6434ad559..77285ca7a 100644 +--- a/lib/bb/_vendor/progressbar/widgets.py ++++ b/lib/bb/_vendor/progressbar/widgets.py +@@ -353,3 +353,39 @@ class BouncingBar(Bar): + if not self.fill_left: rpad, lpad = lpad, rpad + + return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) ++ ++ ++class BouncingSlider(Bar): ++ """ ++ A slider that bounces back and forth in response to update() calls ++ without reference to the actual value. Based on a combination of ++ BouncingBar from a newer version of this module and RotatingMarker. ++ """ ++ def __init__(self, marker='<=>'): ++ self.curmark = -1 ++ self.forward = True ++ Bar.__init__(self, marker=marker) ++ def update(self, pbar, width): ++ left, marker, right = (format_updatable(i, pbar) for i in ++ (self.left, self.marker, self.right)) ++ ++ width -= len(left) + len(right) ++ if width < 0: ++ return '' ++ ++ if pbar.finished: return '%s%s%s' % (left, width * '=', right) ++ ++ self.curmark = self.curmark + 1 ++ position = int(self.curmark % (width * 2 - 1)) ++ if position + len(marker) > width: ++ self.forward = not self.forward ++ self.curmark = 1 ++ position = 1 ++ lpad = ' ' * (position - 1) ++ rpad = ' ' * (width - len(marker) - len(lpad)) ++ ++ if not self.forward: ++ temp = lpad ++ lpad = rpad ++ rpad = temp ++ return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) +-- +2.49.0 + diff --git a/vendor/patches/progressbar-0002-bitbake-Add-initial-pass-of-SPDX-license-headers-to-.patch b/vendor/patches/progressbar-0002-bitbake-Add-initial-pass-of-SPDX-license-headers-to-.patch new file mode 100644 index 000000000..09f68b97b --- /dev/null +++ b/vendor/patches/progressbar-0002-bitbake-Add-initial-pass-of-SPDX-license-headers-to-.patch @@ -0,0 +1,80 @@ +From ff237c33337f4da2ca06c3a2c49699bc26608a6b Mon Sep 17 00:00:00 2001 +From: Richard Purdie +Date: Tue, 30 Apr 2019 11:05:26 +0100 +Subject: [PATCH 2/6] bitbake: Add initial pass of SPDX license headers to + source code + +This adds the SPDX-License-Identifier license headers to the majority of +our source files to make it clearer exactly which license files are under. + +The bulk of the files are under GPL v2.0 with one found to be under V2.0 +or later, some under MIT and some have dual license. There are some files +which are potentially harder to classify where we've imported upstream code +and those can be handled specifically in later commits. + +The COPYING file is replaced with LICENSE.X files which contain the full +license texts. + +Signed-off-by: Richard Purdie +--- + lib/progressbar/__init__.py | 2 ++ + lib/progressbar/compat.py | 2 ++ + lib/progressbar/progressbar.py | 2 ++ + lib/progressbar/widgets.py | 2 ++ + 4 files changed, 8 insertions(+) + +diff --git a/lib/bb/_vendor/progressbar/__init__.py b/lib/bb/_vendor/progressbar/__init__.py +index fbab744ee..c545a6275 100644 +--- a/lib/bb/_vendor/progressbar/__init__.py ++++ b/lib/bb/_vendor/progressbar/__init__.py +@@ -4,6 +4,8 @@ + # progressbar - Text progress bar library for Python. + # Copyright (c) 2005 Nilton Volpato + # ++# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear ++# + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public + # License as published by the Free Software Foundation; either +diff --git a/lib/bb/_vendor/progressbar/compat.py b/lib/bb/_vendor/progressbar/compat.py +index a39f4a1f4..9804e0b51 100644 +--- a/lib/bb/_vendor/progressbar/compat.py ++++ b/lib/bb/_vendor/progressbar/compat.py +@@ -3,6 +3,8 @@ + # progressbar - Text progress bar library for Python. + # Copyright (c) 2005 Nilton Volpato + # ++# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear ++# + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public + # License as published by the Free Software Foundation; either +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index 2873ad6ca..e2b6ba108 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -5,6 +5,8 @@ + # + # (With some small changes after importing into BitBake) + # ++# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear ++# + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public + # License as published by the Free Software Foundation; either +diff --git a/lib/bb/_vendor/progressbar/widgets.py b/lib/bb/_vendor/progressbar/widgets.py +index 77285ca7a..0772aa536 100644 +--- a/lib/bb/_vendor/progressbar/widgets.py ++++ b/lib/bb/_vendor/progressbar/widgets.py +@@ -3,6 +3,8 @@ + # progressbar - Text progress bar library for Python. + # Copyright (c) 2005 Nilton Volpato + # ++# SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause-Clear ++# + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public + # License as published by the Free Software Foundation; either +-- +2.49.0 + diff --git a/vendor/patches/progressbar-0003-bitbake-progressbar-accept-value-over-initial-maxval.patch b/vendor/patches/progressbar-0003-bitbake-progressbar-accept-value-over-initial-maxval.patch new file mode 100644 index 000000000..93a773c8c --- /dev/null +++ b/vendor/patches/progressbar-0003-bitbake-progressbar-accept-value-over-initial-maxval.patch @@ -0,0 +1,55 @@ +From 7cea7f7a87da041fc1ad370c5c3d15aabad3a0d4 Mon Sep 17 00:00:00 2001 +From: Enguerrand de Ribaucourt +Date: Thu, 22 Feb 2024 16:21:53 +0100 +Subject: [PATCH 3/6] bitbake: progressbar: accept value over initial maxval + +There is a very rare case where the maxval is improperly computed +initially for cache loading progress, and the value will go over. + +Explanation from bitbake/lib/bb/cache.py:736 in MulticonfigCache:__init__:progress() + # we might have calculated incorrect total size because a file + # might've been written out just after we checked its size + +In that case, progressbar will receive a value over the initial maxval. +This results in a ValueError stack trace as well as bitbake returning 1. + Traceback (most recent call last): + File ".../poky/bitbake/lib/bb/ui/knotty.py", line 736, in main + cacheprogress.update(event.current) + File ".../poky/bitbake/lib/progressbar/progressbar.py", line 256, in update + raise ValueError('Value out of range') + ValueError: Value out of range + +This fix mirrors the behavior of MulticonfigCache and accepts the new +value as the new maxval. This is also what the percentage printout +is doing in bitbake/lib/progressbar/progressbar.py:191 in ProgressBar:percentage() + +I encountered this issue randomly while working on a project with +VSCode saving files while commands where fired. + +Note: This file is a fork from python-progressbar. It hasn't been +refreshed in 8 years. We did only two commits, 5 years ago with minor +modifications. This new change is also not how the upstream project is +behaving. + +Signed-off-by: Enguerrand de Ribaucourt +Signed-off-by: Richard Purdie +--- + lib/progressbar/progressbar.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index e2b6ba108..d4da10ab7 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -253,7 +253,7 @@ class ProgressBar(object): + if (self.maxval is not widgets.UnknownLength + and not 0 <= value <= self.maxval): + +- raise ValueError('Value out of range') ++ self.maxval = value + + self.currval = value + +-- +2.49.0 + diff --git a/vendor/patches/progressbar-0004-progressbar-Add-self._fd_console-to-use-for-self._ha.patch b/vendor/patches/progressbar-0004-progressbar-Add-self._fd_console-to-use-for-self._ha.patch new file mode 100644 index 000000000..b8eb1e7f3 --- /dev/null +++ b/vendor/patches/progressbar-0004-progressbar-Add-self._fd_console-to-use-for-self._ha.patch @@ -0,0 +1,54 @@ +From f8c76eb89d52b1c28407f0b52dfe4318faa47cd2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Benjamin=20Sz=C5=91ke?= +Date: Sat, 25 Jan 2025 13:27:49 +0100 +Subject: [PATCH 4/6] progressbar: Add self._fd_console to use for + self._handle_resize() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Introduce self._fd_console as a dedicated attribute of self._handle_resize(). + +Signed-off-by: Benjamin Szőke +Signed-off-by: Mathieu Dubois-Briand +--- + lib/progressbar/progressbar.py | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index d4da10ab7..eccc45849 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -110,12 +110,20 @@ class ProgressBar(object): + self.widgets = widgets + self.fd = fd if fd is not None else sys.stderr + self.left_justify = left_justify ++ self._fd_console = None + + self.signal_set = False + if term_width is not None: + self.term_width = term_width + else: + try: ++ # Check if given file descriptor is resizable for example belong ++ # to a terminal/console as STDOUT or STDERR. If file descriptor ++ # is resizable, let's allow to use for self._handle_resize() ++ # in a dedicated self._fd_console in order to be able to set ++ # temporarily/permanently self.fd to any StringIO or other ++ # file descriptor later. ++ self._fd_console = fd + self._handle_resize() + signal.signal(signal.SIGWINCH, self._handle_resize) + self.signal_set = True +@@ -182,7 +189,7 @@ class ProgressBar(object): + def _handle_resize(self, signum=None, frame=None): + """Tries to catch resize signals sent from the terminal.""" + +- h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2] ++ h, w = array('h', ioctl(self._fd_console, termios.TIOCGWINSZ, '\0' * 8))[:2] + self.term_width = w + + +-- +2.49.0 + diff --git a/vendor/patches/progressbar-0005-progressbar-Make-bars-show-correctly-with-maxval-0.patch b/vendor/patches/progressbar-0005-progressbar-Make-bars-show-correctly-with-maxval-0.patch new file mode 100644 index 000000000..1905ca0ba --- /dev/null +++ b/vendor/patches/progressbar-0005-progressbar-Make-bars-show-correctly-with-maxval-0.patch @@ -0,0 +1,32 @@ +From 736b5b33a5d656a6d5cb6a3180ab529832f77b16 Mon Sep 17 00:00:00 2001 +From: Peter Kjellerstedt +Date: Sat, 11 Oct 2025 06:23:10 +0200 +Subject: [PATCH 5/6] progressbar: Make bars show correctly with maxval == 0 + +While it can be argued how useful a progressbar created with 0 as +maximum value is, it should still show two states, started (empty) and +finished (full). Setting the maxval to _DEFAULT_MAXVAL instead will +accomplish this. + +Signed-off-by: Peter Kjellerstedt +Signed-off-by: Mathieu Dubois-Briand +--- + lib/progressbar/progressbar.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index eccc45849..a8e2dc09c 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -106,7 +106,7 @@ class ProgressBar(object): + if widgets is None: + widgets = list(self._DEFAULT_WIDGETS) + +- self.maxval = maxval ++ self.maxval = maxval if maxval != 0 else self._DEFAULT_MAXVAL + self.widgets = widgets + self.fd = fd if fd is not None else sys.stderr + self.left_justify = left_justify +-- +2.49.0 + diff --git a/vendor/patches/progressbar-0006-progressbar-knotty-Allow-mixing-log-messages-and-pro.patch b/vendor/patches/progressbar-0006-progressbar-knotty-Allow-mixing-log-messages-and-pro.patch new file mode 100644 index 000000000..223cb0d29 --- /dev/null +++ b/vendor/patches/progressbar-0006-progressbar-knotty-Allow-mixing-log-messages-and-pro.patch @@ -0,0 +1,32 @@ +From e827db50d8f61f0d04c9bd0753c05aeb1ed0715f Mon Sep 17 00:00:00 2001 +From: Richard Purdie +Date: Tue, 24 Mar 2026 21:37:25 +0000 +Subject: [PATCH 6/6] progressbar/knotty: Allow mixing log messages and + progress bars + +If we try and print a log message in the middle of progress bar, the display +can be corrupted. Add a clear() method to progress bars allowing them to be +removed for the log message, then reprinted using update(). + +Signed-off-by: Richard Purdie +--- + lib/progressbar/progressbar.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/lib/bb/_vendor/progressbar/progressbar.py b/lib/bb/_vendor/progressbar/progressbar.py +index a8e2dc09c..1562774ba 100644 +--- a/lib/bb/_vendor/progressbar/progressbar.py ++++ b/lib/bb/_vendor/progressbar/progressbar.py +@@ -278,6 +278,9 @@ class ProgressBar(object): + self.last_update_time = now + return output + ++ def clear(self): ++ self.fd.write(" " * self.term_width + '\r') ++ self.fd.flush() + + def start(self, update=True): + """Starts measuring time, and prints the bar at 0%. +-- +2.49.0 + From patchwork Wed Jun 24 17:20:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90873 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4960ECDE00A for ; Wed, 24 Jun 2026 17:20:32 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.12752.1782321628238634919 for ; Wed, 24 Jun 2026 10:20:28 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=MPryFUil; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxV2602576 for ; Wed, 24 Jun 2026 10:20:28 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=sLdluIFPn/oSxLzNpYY522wQBZaZjZ5tOco9E5ECa0s=; b= MPryFUil6/kt5s1uZrvfMPL7p3rPj7mefpc6J3pGyu9o1SjVKJvsXkaUWyKNDztZ ZPOzTyGW9o11PyuhsT0iTsSUQ96szR8/p5o1vCQx1iNCBuKAWt4gV1xVac3yqHHI owweb3vs0GgFtjuJXSqNSAHa3BHeCWmoxwcPbhtfVdqKyw96keqYzpXObCjmXuDH TFQF2c8ZuUU/B2PZLmQnIMi7F0mngzMidP04D4NcLOhBCUiB+kCyH3yvw2cuMTs0 YihEGVLCnQLVo5k12k9kVNJ1a7ztoiUosYD4RcfbEl8AoKN6rDr5LEzkUW3wcLFe sZ/jbiQyHJanOTc8eOT9Ug== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-9 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:26 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:13 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:13 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:09 -0700 Subject: [PATCH v4 09/11] vendor.txt: Add typing_extensions for bs4 MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-9-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX5em0YZBYrL+z i6yW0NOwSmjNvyeMccG8gTgShHA3bTEfAGXHM/xYYZX94T01/jbM+YUbcDHyA9BU3sXbNY5dgLz cVgF1JKU2H7G2G9lZkl4mv+MabtMaRj5UYg3xgZTPxs/Q4Ad36iJS7Jx39YHdWHt1DHj4FG4EVj 7cVn+0ikZBP66GAolJ+veOmrA3S6YgE+Idg/jqL3ChJ56axJQNRMoi5cR7e/dIv4eRKvaDoDZte UTxotQHnne6PQcSNNAvgeyQ3BbVOlcv2iIqZO0PXfSsQz5pOPi9/KpnAGZKfldS5/UFKHziFjrt 1c6g7F3VshKSJ//kQ0E38LstgdHX2opozMAjDwL/vWVA0ZJWNSNiSgWMru4z/Q/1EGGAf/AcVpf DDR8LZhSoFMi+QyXXb2ojNYT6xgSig== X-Proofpoint-ORIG-GUID: UZRVs4qvz7NegN2QOk9WjNFx2OPqfxkQ X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX6N5bbTjYgdns O9FrsmnoBU/3cPf+Qj2CZlI08aJqPfkeBzSrDxWlJqULmOcX8qfvSs1/RFJtihuuagJRXQ9Y3bi O5IGTuzk+hcU10LYW3jiFuG8WKqfqj8Ce4OaNDafRoajcKtNfd65 X-Proofpoint-GUID: UZRVs4qvz7NegN2QOk9WjNFx2OPqfxkQ X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11da cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=xWQbeOxxGysSZDUvVC8A:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:32 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19775 Signed-off-by: Rob Woolley --- vendor.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor.txt b/vendor.txt index c60d43092..6ce8846c8 100644 --- a/vendor.txt +++ b/vendor.txt @@ -2,3 +2,4 @@ beautifulsoup4==4.15.0 ply==3.10 progressbar==2.5 simplediff==1.1 +typing_extensions==4.15.0 From patchwork Wed Jun 24 17:20:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90878 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 75CB6CDE000 for ; Wed, 24 Jun 2026 17:20:42 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12961.1782321634270974233 for ; Wed, 24 Jun 2026 10:20:34 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=SYGUbnIg; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxY2602576 for ; Wed, 24 Jun 2026 10:20:34 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=KH+Lzsy1bFhPnmBEeY8RQ6HUR6X1sIsCzR2ztIZiUmU=; b= SYGUbnIgDdoIyM+qhOmffEQO3MNQnNLmHXtoQt3i6FgXzDRIbMJFWKS8WMp8CTCU gggqnx1GgzOB9KyvGxIlfH76Z/HiHZPjcJA2bXykHQqgsWAp7eP8HYprE3c/IJii H6VCyUOlU8a9eTZI+pas2OyBTahEkH97R/t4QT7l0LStuowZuw6J5XDKpdLN5cc8 bSB03vJrNh67iotERImJRpzT6dmXe3KESu8hnzC/TqQZ37yGyzfcafmzOZTB9UHa FhAvJH0CxpymQVQS/iFGZRkuVRBr9nWqsUDrZy9YplfhCr+10cvtt/p++8hI2tOI nFBJ8X8M3aAy8ot4p2TOyA== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-12 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:32 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:13 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:13 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:10 -0700 Subject: [PATCH v4 10/11] Update typing_extensions with vendoring MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-10-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfXxXR5FUz0fsnO RiUJ6n1438jp683my2NS/qr5ugcemv259UkMYvQF5ZNMoVf1DcMIh3+Xv4KB1pZvWRBuAsAJKW7 cu+ioMlGVwXYvRBNKmcENzl19W6qRJSjFsF5oyAp2tB4qA+uCAitOF6qbwmypCjc7rgv22j9TNr UPU0QMD6HrRxtHrXiavd+S2V5pQwWDUFBVCx4WN9OjNeWofNCwWighSM6fcLkMv4kB1JE9riFvW yzru8N5NSVNW8JDCt1feC7LqzSeZAM1m2SRsRkoNvYj/Z2/ZqZ420gZiAEsJOV/z6DpUMjuI0lj ySzp4+mU07RFEno4bRU8SWzH3fXskymHIRVyZbA+53r4C5s1mvda+1w9r7HkMI0IbyDtorUHsEJ hwc+yuSlL58qBCM+yEkUfdhMwYOqGg== X-Proofpoint-ORIG-GUID: xJ-cxH3kKVO_A-eycOhMAF6E9AXBsmqk X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX1leq6dd0/Gdv Ijcz+DncjfXpGpl+m5NeAQjW42Ujqjnu6jglyJlJyzUA+91WgQp2cDPqOGK5MqZZeswnEDEMPO/ Wss2wAkqCc+mcqwk6gGhCfuvBP5itg6ztvssy63nH+DW5XMNejZq X-Proofpoint-GUID: xJ-cxH3kKVO_A-eycOhMAF6E9AXBsmqk X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11e0 cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=fVtmT0gJAAAA:8 a=eN-h0kioAAAA:8 a=8AHkEIZyAAAA:8 a=93Ubh6UdAAAA:8 a=s4Mv0A3cAAAA:8 a=zmLLYQ8cAAAA:8 a=NEAV23lmAAAA:8 a=7PU9pcOHAAAA:20 a=t7CeM3EgAAAA:8 a=1ycBPJ6tAAAA:8 a=qV09NasGAAAA:8 a=l4aTRspXyzLnkhMhUAQA:9 a=YuDXe1IrfG192vdo:21 a=QEXdDO2ut3YA:10 a=O8hF6Hzn-FEA:10 a=Z68JwAoA-C4Plj1ZTcjC:22 a=Q4VSKfms7kB74AhQSI2z:22 a=q-6THwuxr82FYj4XiUVi:22 a=aQocwBtd6-xBJ5fXPnMR:22 a=KWnF-v3VhFgwq6pQ3v6r:22 a=FdTzh2GWekK77mhwV6Dw:22 a=W-HFbg5YRXehYq45iihD:22 a=GlicbclHOgpI_Rq0ze_Y:22 a=bA3UWDv6hWIuX7UZL3qL:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19778 Signed-off-by: Rob Woolley --- lib/bb/_vendor/bs4/_typing.py | 2 +- lib/bb/_vendor/bs4/builder/_html5lib.py | 2 +- lib/bb/_vendor/bs4/builder/_lxml.py | 2 +- lib/bb/_vendor/bs4/dammit.py | 2 +- lib/bb/_vendor/bs4/element.py | 2 +- lib/bb/_vendor/bs4/formatter.py | 2 +- lib/bb/_vendor/typing_extensions.LICENSE | 279 ++ lib/bb/_vendor/typing_extensions.py | 4317 ++++++++++++++++++++++++++++++ lib/bb/_vendor/typing_extensions.pyi | 1 + 9 files changed, 4603 insertions(+), 6 deletions(-) diff --git a/lib/bb/_vendor/bs4/_typing.py b/lib/bb/_vendor/bs4/_typing.py index f3965ccbe..caf2567a6 100644 --- a/lib/bb/_vendor/bs4/_typing.py +++ b/lib/bb/_vendor/bs4/_typing.py @@ -13,7 +13,7 @@ # but it's removed in 3.12, so to support the widest possible set of # versions I'm not using it. -from typing_extensions import ( +from bb._vendor.typing_extensions import ( runtime_checkable, Protocol, TypeAlias, diff --git a/lib/bb/_vendor/bs4/builder/_html5lib.py b/lib/bb/_vendor/bs4/builder/_html5lib.py index 62d4d11f2..2383da93f 100644 --- a/lib/bb/_vendor/bs4/builder/_html5lib.py +++ b/lib/bb/_vendor/bs4/builder/_html5lib.py @@ -16,7 +16,7 @@ from typing import ( Tuple, Union, ) -from typing_extensions import TypeAlias +from bb._vendor.typing_extensions import TypeAlias from bb._vendor.bs4._typing import ( _AttributeValue, _AttributeValues, diff --git a/lib/bb/_vendor/bs4/builder/_lxml.py b/lib/bb/_vendor/bs4/builder/_lxml.py index 4f14bfffa..79ed00e62 100644 --- a/lib/bb/_vendor/bs4/builder/_lxml.py +++ b/lib/bb/_vendor/bs4/builder/_lxml.py @@ -26,7 +26,7 @@ from typing import ( from io import BytesIO from io import StringIO -from typing_extensions import TypeAlias +from bb._vendor.typing_extensions import TypeAlias from lxml import etree # type:ignore from bb._vendor.bs4.element import ( diff --git a/lib/bb/_vendor/bs4/dammit.py b/lib/bb/_vendor/bs4/dammit.py index 4051a9037..cfd05ab12 100644 --- a/lib/bb/_vendor/bs4/dammit.py +++ b/lib/bb/_vendor/bs4/dammit.py @@ -31,7 +31,7 @@ from typing import ( Union, cast, ) -from typing_extensions import Literal +from bb._vendor.typing_extensions import Literal from bb._vendor.bs4._typing import ( _Encoding, _Encodings, diff --git a/lib/bb/_vendor/bs4/element.py b/lib/bb/_vendor/bs4/element.py index 8e63ecd73..e7f97fdee 100644 --- a/lib/bb/_vendor/bs4/element.py +++ b/lib/bb/_vendor/bs4/element.py @@ -41,7 +41,7 @@ from typing import ( cast, overload, ) -from typing_extensions import ( +from bb._vendor.typing_extensions import ( Self, TypeAlias, ) diff --git a/lib/bb/_vendor/bs4/formatter.py b/lib/bb/_vendor/bs4/formatter.py index ef69d992f..15e6e298c 100644 --- a/lib/bb/_vendor/bs4/formatter.py +++ b/lib/bb/_vendor/bs4/formatter.py @@ -1,6 +1,6 @@ from __future__ import annotations from typing import Callable, Dict, Iterable, Optional, Set, Tuple, TYPE_CHECKING, Union -from typing_extensions import TypeAlias +from bb._vendor.typing_extensions import TypeAlias from bb._vendor.bs4.dammit import EntitySubstitution if TYPE_CHECKING: diff --git a/lib/bb/_vendor/typing_extensions.LICENSE b/lib/bb/_vendor/typing_extensions.LICENSE new file mode 100644 index 000000000..f26bcf4d2 --- /dev/null +++ b/lib/bb/_vendor/typing_extensions.LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/lib/bb/_vendor/typing_extensions.py b/lib/bb/_vendor/typing_extensions.py new file mode 100644 index 000000000..51683f226 --- /dev/null +++ b/lib/bb/_vendor/typing_extensions.py @@ -0,0 +1,4317 @@ +import abc +import builtins +import collections +import collections.abc +import contextlib +import enum +import functools +import inspect +import io +import keyword +import operator +import sys +import types as _types +import typing +import warnings + +# Breakpoint: https://github.com/python/cpython/pull/119891 +if sys.version_info >= (3, 14): + import annotationlib + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', + 'Self', + 'Type', + 'TypeVar', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'Buffer', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'NamedTuple', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsIndex', + 'SupportsInt', + 'SupportsRound', + 'Reader', + 'Writer', + + # One-off things. + 'Annotated', + 'assert_never', + 'assert_type', + 'clear_overloads', + 'dataclass_transform', + 'deprecated', + 'disjoint_base', + 'Doc', + 'evaluate_forward_ref', + 'get_overloads', + 'final', + 'Format', + 'get_annotations', + 'get_args', + 'get_origin', + 'get_original_bases', + 'get_protocol_members', + 'get_type_hints', + 'IntVar', + 'is_protocol', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'override', + 'Protocol', + 'Sentinel', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeAliasType', + 'TypeForm', + 'TypeGuard', + 'TypeIs', + 'TYPE_CHECKING', + 'type_repr', + 'Never', + 'NoReturn', + 'ReadOnly', + 'Required', + 'NotRequired', + 'NoDefault', + 'NoExtraItems', + + # Pure aliases, have always been in typing + 'AbstractSet', + 'AnyStr', + 'BinaryIO', + 'Callable', + 'Collection', + 'Container', + 'Dict', + 'ForwardRef', + 'FrozenSet', + 'Generator', + 'Generic', + 'Hashable', + 'IO', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'List', + 'Mapping', + 'MappingView', + 'Match', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Optional', + 'Pattern', + 'Reversible', + 'Sequence', + 'Set', + 'Sized', + 'TextIO', + 'Tuple', + 'Union', + 'ValuesView', + 'cast', + 'no_type_check', + 'no_type_check_decorator', +] + +# for backward compatibility +PEP_560 = True +GenericMeta = type +# Breakpoint: https://github.com/python/cpython/pull/116129 +_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta") + +# Added with bpo-45166 to 3.10.1+ and some 3.9 versions +_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__ + +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. + + +class _Sentinel: + def __repr__(self): + return "" + + +_marker = _Sentinel() + + +# Breakpoint: https://github.com/python/cpython/pull/27342 +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +else: + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) + + +NoReturn = typing.NoReturn + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + + +# Breakpoint: https://github.com/python/cpython/pull/31841 +if sys.version_info >= (3, 11): + from typing import Any +else: + + class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing_extensions.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + if self is Any: + return "typing_extensions.Any" + return super().__repr__() + + class Any(metaclass=_AnyMeta): + """Special type indicating an unconstrained type. + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + checks. + """ + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return super().__new__(cls, *args, **kwargs) + + +ClassVar = typing.ClassVar + +# Vendored from cpython typing._SpecialFrom +# Having a separate class means that instances will not be rejected by +# typing._type_check. +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + +# Note that inheriting from this class means that the object will be +# rejected by typing._type_check, so do not use it if the special form +# is arguably valid as a type by itself. +class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + +Final = typing.Final + +# Breakpoint: https://github.com/python/cpython/pull/30530 +if sys.version_info >= (3, 11): + final = typing.final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +if hasattr(typing, "disjoint_base"): # 3.15 + disjoint_base = typing.disjoint_base +else: + def disjoint_base(cls): + """This decorator marks a class as a disjoint base. + + Child classes of a disjoint base cannot inherit from other disjoint bases that are + not parent classes of the disjoint base. + + For example: + + @disjoint_base + class Disjoint1: pass + + @disjoint_base + class Disjoint2: pass + + class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error + + Type checkers can use knowledge of disjoint bases to detect unreachable code + and determine when two types can overlap. + + See PEP 800.""" + cls.__disjoint_base__ = True + return cls + + +def IntVar(name): + return typing.TypeVar(name) + + +# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8 +# Breakpoint: https://github.com/python/cpython/pull/29334 +if sys.version_info >= (3, 10, 1): + Literal = typing.Literal +else: + def _flatten_literal_params(parameters): + """An internal helper for Literal creation: flatten Literals among parameters""" + params = [] + for p in parameters: + if isinstance(p, _LiteralGenericAlias): + params.extend(p.__args__) + else: + params.append(p) + return tuple(params) + + def _value_and_type_iter(params): + for p in params: + yield p, type(p) + + class _LiteralGenericAlias(typing._GenericAlias, _root=True): + def __eq__(self, other): + if not isinstance(other, _LiteralGenericAlias): + return NotImplemented + these_args_deduped = set(_value_and_type_iter(self.__args__)) + other_args_deduped = set(_value_and_type_iter(other.__args__)) + return these_args_deduped == other_args_deduped + + def __hash__(self): + return hash(frozenset(_value_and_type_iter(self.__args__))) + + class _LiteralForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, doc: str): + self._name = 'Literal' + self._doc = self.__doc__ = doc + + def __getitem__(self, parameters): + if not isinstance(parameters, tuple): + parameters = (parameters,) + + parameters = _flatten_literal_params(parameters) + + val_type_pairs = list(_value_and_type_iter(parameters)) + try: + deduped_pairs = set(val_type_pairs) + except TypeError: + # unhashable parameters + pass + else: + # similar logic to typing._deduplicate on Python 3.9+ + if len(deduped_pairs) < len(val_type_pairs): + new_parameters = [] + for pair in val_type_pairs: + if pair in deduped_pairs: + new_parameters.append(pair[0]) + deduped_pairs.remove(pair) + assert not deduped_pairs, deduped_pairs + parameters = tuple(new_parameters) + + return _LiteralGenericAlias(self, parameters) + + Literal = _LiteralForm(doc="""\ + A type that can be used to indicate to type checkers + that the corresponding value has a value literally equivalent + to the provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to + the value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime + checking verifying that the parameter is actually a value + instead of a type.""") + + +_overload_dummy = typing._overload_dummy + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() + + +# This is not a real generic class. Don't use outside annotations. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator +Deque = typing.Deque +DefaultDict = typing.DefaultDict +OrderedDict = typing.OrderedDict +Counter = typing.Counter +ChainMap = typing.ChainMap +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +# Breakpoint: https://github.com/python/cpython/pull/118681 +if sys.version_info >= (3, 13, 0, "beta"): + from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator +else: + def _is_dunder(attr): + return attr.startswith('__') and attr.endswith('__') + + + class _SpecialGenericAlias(typing._SpecialGenericAlias, _root=True): + def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()): + super().__init__(origin, nparams, inst=inst, name=name) + self._defaults = defaults + + def __setattr__(self, attr, val): + allowed_attrs = {'_name', '_inst', '_nparams', '_defaults'} + if _is_dunder(attr) or attr in allowed_attrs: + object.__setattr__(self, attr, val) + else: + setattr(self.__origin__, attr, val) + + @typing._tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) + if ( + self._defaults + and len(params) < self._nparams + and len(params) + len(self._defaults) >= self._nparams + ): + params = (*params, *self._defaults[len(params) - self._nparams:]) + actual_len = len(params) + + if actual_len != self._nparams: + if self._defaults: + expected = f"at least {self._nparams - len(self._defaults)}" + else: + expected = str(self._nparams) + if not self._nparams: + raise TypeError(f"{self} is not a generic class") + raise TypeError( + f"Too {'many' if actual_len > self._nparams else 'few'}" + f" arguments for {self};" + f" actual {actual_len}, expected {expected}" + ) + return self.copy_with(params) + + _NoneType = type(None) + Generator = _SpecialGenericAlias( + collections.abc.Generator, 3, defaults=(_NoneType, _NoneType) + ) + AsyncGenerator = _SpecialGenericAlias( + collections.abc.AsyncGenerator, 2, defaults=(_NoneType,) + ) + ContextManager = _SpecialGenericAlias( + contextlib.AbstractContextManager, + 2, + name="ContextManager", + defaults=(typing.Optional[bool],) + ) + AsyncContextManager = _SpecialGenericAlias( + contextlib.AbstractAsyncContextManager, + 2, + name="AsyncContextManager", + defaults=(typing.Optional[bool],) + ) + + +_PROTO_ALLOWLIST = { + 'collections.abc': [ + 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer', + ], + 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], + 'typing_extensions': ['Buffer'], +} + + +_EXCLUDED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | { + "__match_args__", "__protocol_attrs__", "__non_callable_proto_members__", + "__final__", +} + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in {'Protocol', 'Generic'}: + continue + annotations = getattr(base, '__annotations__', {}) + for attr in (*base.__dict__, *annotations): + if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS): + attrs.add(attr) + return attrs + + +def _caller(depth=1, default='__main__'): + try: + return sys._getframemodulename(depth + 1) or default + except AttributeError: # For platforms without _getframemodulename() + pass + try: + return sys._getframe(depth + 1).f_globals.get('__name__', default) + except (AttributeError, ValueError): # For platforms without _getframe() + pass + return None + + +# `__match_args__` attribute was removed from protocol members in 3.13, +# we want to backport this change to older Python versions. +# Breakpoint: https://github.com/python/cpython/pull/110683 +if sys.version_info >= (3, 13): + Protocol = typing.Protocol +else: + def _allow_reckless_class_checks(depth=2): + """Allow instance and class checks for special stdlib modules. + The abc and functools modules indiscriminately call isinstance() and + issubclass() on the whole MRO of a user class, which may contain protocols. + """ + return _caller(depth) in {'abc', 'functools', None} + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + def _type_check_issubclass_arg_1(arg): + """Raise TypeError if `arg` is not an instance of `type` + in `issubclass(arg, )`. + + In most cases, this is verified by type.__subclasscheck__. + Checking it again unnecessarily would slow down issubclass() checks, + so, we don't perform this check unless we absolutely have to. + + For various error paths, however, + we want to ensure that *this* error message is shown to the user + where relevant, rather than a typing.py-specific error message. + """ + if not isinstance(arg, type): + # Same error message as for issubclass(1, int). + raise TypeError('issubclass() arg 1 must be a class') + + # Inheriting from typing._ProtocolMeta isn't actually desirable, + # but is necessary to allow typing.Protocol and typing_extensions.Protocol + # to mix without getting TypeErrors about "metaclass conflict" + class _ProtocolMeta(type(typing.Protocol)): + # This metaclass is somewhat unfortunate, + # but is necessary for several reasons... + # + # NOTE: DO NOT call super() in any methods in this class + # That would call the methods on typing._ProtocolMeta on Python <=3.11 + # and those are slow + def __new__(mcls, name, bases, namespace, **kwargs): + if name == "Protocol" and len(bases) < 2: + pass + elif {Protocol, typing.Protocol} & set(bases): + for base in bases: + if not ( + base in {object, typing.Generic, Protocol, typing.Protocol} + or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, []) + or is_protocol(base) + ): + raise TypeError( + f"Protocols can only inherit from other protocols, " + f"got {base!r}" + ) + return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs) + + def __init__(cls, *args, **kwargs): + abc.ABCMeta.__init__(cls, *args, **kwargs) + if getattr(cls, "_is_protocol", False): + cls.__protocol_attrs__ = _get_protocol_attrs(cls) + + def __subclasscheck__(cls, other): + if cls is Protocol: + return type.__subclasscheck__(cls, other) + if ( + getattr(cls, '_is_protocol', False) + and not _allow_reckless_class_checks() + ): + if not getattr(cls, '_is_runtime_protocol', False): + _type_check_issubclass_arg_1(other) + raise TypeError( + "Instance and class checks can only be used with " + "@runtime_checkable protocols" + ) + if ( + # this attribute is set by @runtime_checkable: + cls.__non_callable_proto_members__ + and cls.__dict__.get("__subclasshook__") is _proto_hook + ): + _type_check_issubclass_arg_1(other) + non_method_attrs = sorted(cls.__non_callable_proto_members__) + raise TypeError( + "Protocols with non-method members don't support issubclass()." + f" Non-method members: {str(non_method_attrs)[1:-1]}." + ) + return abc.ABCMeta.__subclasscheck__(cls, other) + + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if cls is Protocol: + return type.__instancecheck__(cls, instance) + if not getattr(cls, "_is_protocol", False): + # i.e., it's a concrete subclass of a protocol + return abc.ABCMeta.__instancecheck__(cls, instance) + + if ( + not getattr(cls, '_is_runtime_protocol', False) and + not _allow_reckless_class_checks() + ): + raise TypeError("Instance and class checks can only be used with" + " @runtime_checkable protocols") + + if abc.ABCMeta.__instancecheck__(cls, instance): + return True + + for attr in cls.__protocol_attrs__: + try: + val = inspect.getattr_static(instance, attr) + except AttributeError: + break + # this attribute is set by @runtime_checkable: + if val is None and attr not in cls.__non_callable_proto_members__: + break + else: + return True + + return False + + def __eq__(cls, other): + # Hack so that typing.Generic.__class_getitem__ + # treats typing_extensions.Protocol + # as equivalent to typing.Protocol + if abc.ABCMeta.__eq__(cls, other) is True: + return True + return cls is Protocol and other is typing.Protocol + + # This has to be defined, or the abc-module cache + # complains about classes with this metaclass being unhashable, + # if we define only __eq__! + def __hash__(cls) -> int: + return type.__hash__(cls) + + @classmethod + def _proto_hook(cls, other): + if not cls.__dict__.get('_is_protocol', False): + return NotImplemented + + for attr in cls.__protocol_attrs__: + for base in other.__mro__: + # Check if the members appears in the class dictionary... + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + + # ...or in annotations, if it is a sub-protocol. + annotations = getattr(base, '__annotations__', {}) + if ( + isinstance(annotations, collections.abc.Mapping) + and attr in annotations + and is_protocol(other) + ): + break + else: + return NotImplemented + return True + + class Protocol(typing.Generic, metaclass=_ProtocolMeta): + __doc__ = typing.Protocol.__doc__ + __slots__ = () + _is_protocol = True + _is_runtime_protocol = False + + def __init_subclass__(cls, *args, **kwargs): + super().__init_subclass__(*args, **kwargs) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', False): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # Prohibit instantiation for protocol classes + if cls._is_protocol and cls.__init__ is Protocol.__init__: + cls.__init__ = _no_init + + +# Breakpoint: https://github.com/python/cpython/pull/113401 +if sys.version_info >= (3, 13): + runtime_checkable = typing.runtime_checkable +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol. + + Such protocol can be used with isinstance() and issubclass(). + Raise TypeError if applied to a non-protocol class. + This allows a simple-minded structural check very similar to + one trick ponies in collections.abc such as Iterable. + + For example:: + + @runtime_checkable + class Closable(Protocol): + def close(self): ... + + assert isinstance(open('/some/file'), Closable) + + Warning: this will check only the presence of the required methods, + not their type signatures! + """ + if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False): + raise TypeError(f'@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + + # typing.Protocol classes on <=3.11 break if we execute this block, + # because typing.Protocol classes on <=3.11 don't have a + # `__protocol_attrs__` attribute, and this block relies on the + # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+ + # break if we *don't* execute this block, because *they* assume that all + # protocol classes have a `__non_callable_proto_members__` attribute + # (which this block sets) + if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2): + # PEP 544 prohibits using issubclass() + # with protocols that have non-method members. + # See gh-113320 for why we compute this attribute here, + # rather than in `_ProtocolMeta.__init__` + cls.__non_callable_proto_members__ = set() + for attr in cls.__protocol_attrs__: + try: + is_callable = callable(getattr(cls, attr, None)) + except Exception as e: + raise TypeError( + f"Failed to determine whether protocol member {attr!r} " + "is a method member" + ) from e + else: + if not is_callable: + cls.__non_callable_proto_members__.add(attr) + + return cls + + +# The "runtime" alias exists for backwards compatibility. +runtime = runtime_checkable + + +# Our version of runtime-checkable protocols is faster on Python <=3.11 +# Breakpoint: https://github.com/python/cpython/pull/112717 +if sys.version_info >= (3, 12): + SupportsInt = typing.SupportsInt + SupportsFloat = typing.SupportsFloat + SupportsComplex = typing.SupportsComplex + SupportsBytes = typing.SupportsBytes + SupportsIndex = typing.SupportsIndex + SupportsAbs = typing.SupportsAbs + SupportsRound = typing.SupportsRound +else: + @runtime_checkable + class SupportsInt(Protocol): + """An ABC with one abstract method __int__.""" + __slots__ = () + + @abc.abstractmethod + def __int__(self) -> int: + pass + + @runtime_checkable + class SupportsFloat(Protocol): + """An ABC with one abstract method __float__.""" + __slots__ = () + + @abc.abstractmethod + def __float__(self) -> float: + pass + + @runtime_checkable + class SupportsComplex(Protocol): + """An ABC with one abstract method __complex__.""" + __slots__ = () + + @abc.abstractmethod + def __complex__(self) -> complex: + pass + + @runtime_checkable + class SupportsBytes(Protocol): + """An ABC with one abstract method __bytes__.""" + __slots__ = () + + @abc.abstractmethod + def __bytes__(self) -> bytes: + pass + + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + @runtime_checkable + class SupportsAbs(Protocol[T_co]): + """ + An ABC with one abstract method __abs__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __abs__(self) -> T_co: + pass + + @runtime_checkable + class SupportsRound(Protocol[T_co]): + """ + An ABC with one abstract method __round__ that is covariant in its return type. + """ + __slots__ = () + + @abc.abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +if hasattr(io, "Reader") and hasattr(io, "Writer"): + Reader = io.Reader + Writer = io.Writer +else: + @runtime_checkable + class Reader(Protocol[T_co]): + """Protocol for simple I/O reader instances. + + This protocol only supports blocking I/O. + """ + + __slots__ = () + + @abc.abstractmethod + def read(self, size: int = ..., /) -> T_co: + """Read data from the input stream and return it. + + If *size* is specified, at most *size* items (bytes/characters) will be + read. + """ + + @runtime_checkable + class Writer(Protocol[T_contra]): + """Protocol for simple I/O writer instances. + + This protocol only supports blocking I/O. + """ + + __slots__ = () + + @abc.abstractmethod + def write(self, data: T_contra, /) -> int: + """Write *data* to the output stream and return the number of items written.""" # noqa: E501 + + +_NEEDS_SINGLETONMETA = ( + not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems") +) + +if _NEEDS_SINGLETONMETA: + class SingletonMeta(type): + def __setattr__(cls, attr, value): + # TypeError is consistent with the behavior of NoneType + raise TypeError( + f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}" + ) + + +if hasattr(typing, "NoDefault"): + NoDefault = typing.NoDefault +else: + class NoDefaultType(metaclass=SingletonMeta): + """The type of the NoDefault singleton.""" + + __slots__ = () + + def __new__(cls): + return globals().get("NoDefault") or object.__new__(cls) + + def __repr__(self): + return "typing_extensions.NoDefault" + + def __reduce__(self): + return "NoDefault" + + NoDefault = NoDefaultType() + del NoDefaultType + +if hasattr(typing, "NoExtraItems"): + NoExtraItems = typing.NoExtraItems +else: + class NoExtraItemsType(metaclass=SingletonMeta): + """The type of the NoExtraItems singleton.""" + + __slots__ = () + + def __new__(cls): + return globals().get("NoExtraItems") or object.__new__(cls) + + def __repr__(self): + return "typing_extensions.NoExtraItems" + + def __reduce__(self): + return "NoExtraItems" + + NoExtraItems = NoExtraItemsType() + del NoExtraItemsType + +if _NEEDS_SINGLETONMETA: + del SingletonMeta + + +# Update this to something like >=3.13.0b1 if and when +# PEP 728 is implemented in CPython +_PEP_728_IMPLEMENTED = False + +if _PEP_728_IMPLEMENTED: + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. + # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11. + # Aaaand on 3.12 we add __orig_bases__ to TypedDict + # to enable better runtime introspection. + # On 3.13 we deprecate some odd ways of creating TypedDicts. + # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier. + # PEP 728 (still pending) makes more changes. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + # 3.10.0 and later + _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters + + def _get_typeddict_qualifiers(annotation_type): + while True: + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + else: + break + elif annotation_origin is Required: + yield Required + annotation_type, = get_args(annotation_type) + elif annotation_origin is NotRequired: + yield NotRequired + annotation_type, = get_args(annotation_type) + elif annotation_origin is ReadOnly: + yield ReadOnly + annotation_type, = get_args(annotation_type) + else: + break + + class _TypedDictMeta(type): + + def __new__(cls, name, bases, ns, *, total=True, closed=None, + extra_items=NoExtraItems): + """Create new typed dict class object. + + This method is called when TypedDict is subclassed, + or when TypedDict is instantiated. This way + TypedDict supports all three syntax forms described in its docstring. + Subclasses and instances of TypedDict return actual dictionaries. + """ + for base in bases: + if type(base) is not _TypedDictMeta and base is not typing.Generic: + raise TypeError('cannot inherit from both a TypedDict type ' + 'and a non-TypedDict base class') + if closed is not None and extra_items is not NoExtraItems: + raise TypeError(f"Cannot combine closed={closed!r} and extra_items") + + if any(issubclass(b, typing.Generic) for b in bases): + generic_base = (typing.Generic,) + else: + generic_base = () + + ns_annotations = ns.pop('__annotations__', None) + + # typing.py generally doesn't let you inherit from plain Generic, unless + # the name of the class happens to be "Protocol" + tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns) + tp_dict.__name__ = name + if tp_dict.__qualname__ == "Protocol": + tp_dict.__qualname__ = name + + if not hasattr(tp_dict, '__orig_bases__'): + tp_dict.__orig_bases__ = bases + + annotations = {} + own_annotate = None + if ns_annotations is not None: + own_annotations = ns_annotations + elif sys.version_info >= (3, 14): + if hasattr(annotationlib, "get_annotate_from_class_namespace"): + own_annotate = annotationlib.get_annotate_from_class_namespace(ns) + else: + # 3.14.0a7 and earlier + own_annotate = ns.get("__annotate__") + if own_annotate is not None: + own_annotations = annotationlib.call_annotate_function( + own_annotate, Format.FORWARDREF, owner=tp_dict + ) + else: + own_annotations = {} + else: + own_annotations = {} + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + if _TAKES_MODULE: + own_checked_annotations = { + n: typing._type_check(tp, msg, module=tp_dict.__module__) + for n, tp in own_annotations.items() + } + else: + own_checked_annotations = { + n: typing._type_check(tp, msg) + for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + readonly_keys = set() + mutable_keys = set() + extra_items_type = extra_items + + for base in bases: + base_dict = base.__dict__ + + if sys.version_info <= (3, 14): + annotations.update(base_dict.get('__annotations__', {})) + required_keys.update(base_dict.get('__required_keys__', ())) + optional_keys.update(base_dict.get('__optional_keys__', ())) + readonly_keys.update(base_dict.get('__readonly_keys__', ())) + mutable_keys.update(base_dict.get('__mutable_keys__', ())) + + # This was specified in an earlier version of PEP 728. Support + # is retained for backwards compatibility, but only for Python + # 3.13 and lower. + if (closed and sys.version_info < (3, 14) + and "__extra_items__" in own_checked_annotations): + annotation_type = own_checked_annotations.pop("__extra_items__") + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + if Required in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "Required" + ) + if NotRequired in qualifiers: + raise TypeError( + "Special key __extra_items__ does not support " + "NotRequired" + ) + extra_items_type = annotation_type + + annotations.update(own_checked_annotations) + for annotation_key, annotation_type in own_checked_annotations.items(): + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + + if Required in qualifiers: + required_keys.add(annotation_key) + elif NotRequired in qualifiers: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) + if ReadOnly in qualifiers: + mutable_keys.discard(annotation_key) + readonly_keys.add(annotation_key) + else: + mutable_keys.add(annotation_key) + readonly_keys.discard(annotation_key) + + # Breakpoint: https://github.com/python/cpython/pull/119891 + if sys.version_info >= (3, 14): + def __annotate__(format): + annos = {} + for base in bases: + if base is Generic: + continue + base_annotate = base.__annotate__ + if base_annotate is None: + continue + base_annos = annotationlib.call_annotate_function( + base_annotate, format, owner=base) + annos.update(base_annos) + if own_annotate is not None: + own = annotationlib.call_annotate_function( + own_annotate, format, owner=tp_dict) + if format != Format.STRING: + own = { + n: typing._type_check(tp, msg, module=tp_dict.__module__) + for n, tp in own.items() + } + elif format == Format.STRING: + own = annotationlib.annotations_to_string(own_annotations) + elif format in (Format.FORWARDREF, Format.VALUE): + own = own_checked_annotations + else: + raise NotImplementedError(format) + annos.update(own) + return annos + + tp_dict.__annotate__ = __annotate__ + else: + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + tp_dict.__readonly_keys__ = frozenset(readonly_keys) + tp_dict.__mutable_keys__ = frozenset(mutable_keys) + tp_dict.__total__ = total + tp_dict.__closed__ = closed + tp_dict.__extra_items__ = extra_items_type + return tp_dict + + __call__ = dict # static method + + def __subclasscheck__(cls, other): + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + + __instancecheck__ = __subclasscheck__ + + _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) + + def _create_typeddict( + typename, + fields, + /, + *, + typing_is_inline, + total, + closed, + extra_items, + **kwargs, + ): + if fields is _marker or fields is None: + if fields is _marker: + deprecated_thing = ( + "Failing to pass a value for the 'fields' parameter" + ) + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + + example = f"`{typename} = TypedDict({typename!r}, {{}})`" + deprecation_msg = ( + f"{deprecated_thing} is deprecated and will be disallowed in " + "Python 3.15. To create a TypedDict class with 0 fields " + "using the functional syntax, pass an empty dictionary, e.g. " + ) + example + "." + warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2) + # Support a field called "closed" + if closed is not False and closed is not True and closed is not None: + kwargs["closed"] = closed + closed = None + # Or "extra_items" + if extra_items is not NoExtraItems: + kwargs["extra_items"] = extra_items + extra_items = NoExtraItems + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + if kwargs: + # Breakpoint: https://github.com/python/cpython/pull/104891 + if sys.version_info >= (3, 13): + raise TypeError("TypedDict takes no keyword arguments") + warnings.warn( + "The kwargs-based syntax for TypedDict definitions is deprecated " + "in Python 3.11, will be removed in Python 3.13, and may not be " + "understood by third-party type checkers.", + DeprecationWarning, + stacklevel=2, + ) + + ns = {'__annotations__': dict(fields)} + module = _caller(depth=4 if typing_is_inline else 2) + if module is not None: + # Setting correct module is necessary to make typed dict classes + # pickleable. + ns['__module__'] = module + + td = _TypedDictMeta(typename, (), ns, total=total, closed=closed, + extra_items=extra_items) + td.__orig_bases__ = (TypedDict,) + return td + + class _TypedDictSpecialForm(_SpecialForm, _root=True): + def __call__( + self, + typename, + fields=_marker, + /, + *, + total=True, + closed=None, + extra_items=NoExtraItems, + **kwargs + ): + return _create_typeddict( + typename, + fields, + typing_is_inline=False, + total=total, + closed=closed, + extra_items=extra_items, + **kwargs, + ) + + def __mro_entries__(self, bases): + return (_TypedDict,) + + @_TypedDictSpecialForm + def TypedDict(self, args): + """A simple typed namespace. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type such that a type checker will expect all + instances to have a certain set of keys, where each key is + associated with a value of a consistent type. This expectation + is not checked at runtime. + + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports an additional equivalent form:: + + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + By default, all keys must be present in a TypedDict. It is possible + to override this by specifying totality:: + + class Point2D(TypedDict, total=False): + x: int + y: int + + This means that a Point2D TypedDict can have any of the keys omitted. A type + checker is only expected to support a literal False or True as the value of + the total argument. True is the default, and makes all items defined in the + class body be required. + + The Required and NotRequired special forms can also be used to mark + individual keys as being required or not required:: + + class Point2D(TypedDict): + x: int # the "x" key must always be present (Required is the default) + y: NotRequired[int] # the "y" key can be omitted + + See PEP 655 for more details on Required and NotRequired. + """ + # This runs when creating inline TypedDicts: + if not isinstance(args, dict): + raise TypeError( + "TypedDict[...] should be used with a single dict argument" + ) + + return _create_typeddict( + "", + args, + typing_is_inline=True, + total=True, + closed=True, + extra_items=NoExtraItems, + ) + + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + return isinstance(tp, _TYPEDDICT_TYPES) + + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(val, typ, /): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return val + + +if hasattr(typing, "ReadOnly"): # 3.13+ + get_type_hints = typing.get_type_hints +else: # <=3.13 + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, typing._AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return _types.GenericAlias(t.__origin__, stripped_args) + if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + # Breakpoint: https://github.com/python/cpython/pull/30304 + if sys.version_info < (3, 11): + _clean_optional(obj, hint, globalns, localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + _NoneType = type(None) + + def _could_be_inserted_optional(t): + """detects Union[..., None] pattern""" + if not isinstance(t, typing._UnionGenericAlias): + return False + # Assume if last argument is not None they are user defined + if t.__args__[-1] is not _NoneType: + return False + return True + + # < 3.11 + def _clean_optional(obj, hints, globalns=None, localns=None): + # reverts injected Union[..., None] cases from typing.get_type_hints + # when a None default value is used. + # see https://github.com/python/typing_extensions/issues/310 + if not hints or isinstance(obj, type): + return + defaults = typing._get_defaults(obj) # avoid accessing __annotations___ + if not defaults: + return + original_hints = obj.__annotations__ + for name, value in hints.items(): + # Not a Union[..., None] or replacement conditions not fullfilled + if (not _could_be_inserted_optional(value) + or name not in defaults + or defaults[name] is not None + ): + continue + original_value = original_hints[name] + # value=NoneType should have caused a skip above but check for safety + if original_value is None: + original_value = _NoneType + # Forward reference + if isinstance(original_value, str): + if globalns is None: + if isinstance(obj, _types.ModuleType): + globalns = obj.__dict__ + else: + nsobj = obj + # Find globalns for the unwrapped object. + while hasattr(nsobj, '__wrapped__'): + nsobj = nsobj.__wrapped__ + globalns = getattr(nsobj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + + original_value = ForwardRef( + original_value, + is_argument=not isinstance(obj, _types.ModuleType) + ) + original_evaluated = typing._eval_type(original_value, globalns, localns) + # Compare if values differ. Note that even if equal + # value might be cached by typing._tp_cache contrary to original_evaluated + if original_evaluated != value or ( + # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias + hasattr(_types, "UnionType") + and isinstance(original_evaluated, _types.UnionType) + and not isinstance(value, _types.UnionType) + ): + hints[name] = original_evaluated + +# Python 3.9 has get_origin() and get_args() but those implementations don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +# Breakpoint: https://github.com/python/cpython/pull/25298 +if sys.version_info >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.9 +else: + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, typing._AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._BaseGenericAlias, _types.GenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, typing._AnnotatedAlias): + return (tp.__origin__, *tp.__metadata__) + if isinstance(tp, (typing._GenericAlias, _types.GenericAlias)): + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +else: + @_ExtensionsSpecialForm + def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") + + +def _set_default(type_param, default): + type_param.has_default = lambda: default is not NoDefault + type_param.__default__ = default + + +def _set_module(typevarlike): + # for pickling: + def_mod = _caller(depth=2) + if def_mod != 'typing_extensions': + typevarlike.__module__ = def_mod + + +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + __slots__ = () + __init__ = _set_default + + +# Classes using this metaclass must provide a _backported_typevarlike ClassVar +class _TypeVarLikeMeta(type): + def __instancecheck__(cls, __instance: Any) -> bool: + return isinstance(__instance, cls._backported_typevarlike) + + +if _PEP_696_IMPLEMENTED: + from typing import TypeVar +else: + # Add default and infer_variance parameters from PEP 696 and 695 + class TypeVar(metaclass=_TypeVarLikeMeta): + """Type variable.""" + + _backported_typevarlike = typing.TypeVar + + def __new__(cls, name, *constraints, bound=None, + covariant=False, contravariant=False, + default=NoDefault, infer_variance=False): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant, + infer_variance=infer_variance) + else: + typevar = typing.TypeVar(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + if infer_variance and (covariant or contravariant): + raise ValueError("Variance cannot be specified with infer_variance.") + typevar.__infer_variance__ = infer_variance + + _set_default(typevar, default) + _set_module(typevar) + + def _tvar_prepare_subst(alias, args): + if ( + typevar.has_default() + and alias.__parameters__.index(typevar) == len(args) + ): + args += (typevar.__default__,) + return args + + typevar.__typing_prepare_subst__ = _tvar_prepare_subst + return typevar + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type") + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + + +if _PEP_696_IMPLEMENTED: + from typing import ParamSpec + +# 3.10+ +elif hasattr(typing, 'ParamSpec'): + + # Add default parameter - PEP 696 + class ParamSpec(metaclass=_TypeVarLikeMeta): + """Parameter specification.""" + + _backported_typevarlike = typing.ParamSpec + + def __new__(cls, name, *, bound=None, + covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + if hasattr(typing, "TypeAliasType"): + # PEP 695 implemented, can pass infer_variance to typing.TypeVar + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant, + infer_variance=infer_variance) + else: + paramspec = typing.ParamSpec(name, bound=bound, + covariant=covariant, + contravariant=contravariant) + paramspec.__infer_variance__ = infer_variance + + _set_default(paramspec, default) + _set_module(paramspec) + + def _paramspec_prepare_subst(alias, args): + params = alias.__parameters__ + i = params.index(paramspec) + if i == len(args) and paramspec.has_default(): + args = [*args, paramspec.__default__] + if i >= len(args): + raise TypeError(f"Too few arguments for {alias}") + # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. + if len(params) == 1 and not typing._is_param_expr(args[0]): + assert i == 0 + args = (args,) + # Convert lists to tuples to help other libraries cache the results. + elif isinstance(args[i], list): + args = (*args[:i], tuple(args[i]), *args[i + 1:]) + return args + + paramspec.__typing_prepare_subst__ = _paramspec_prepare_subst + return paramspec + + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") + +# 3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list, _DefaultMixin): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + infer_variance=False, default=NoDefault): + list.__init__(self, [self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + self.__infer_variance__ = bool(infer_variance) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__infer_variance__: + prefix = '' + elif self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + +# 3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + + # 3.9.0-1 + if not hasattr(typing, '_type_convert'): + def _type_convert(arg, module=None, *, allow_special_forms=False): + """For converting None to type(None), and strings to ForwardRef.""" + if arg is None: + return type(None) + if isinstance(arg, str): + if sys.version_info <= (3, 9, 6): + return ForwardRef(arg) + if sys.version_info <= (3, 9, 7): + return ForwardRef(arg, module=module) + return ForwardRef(arg, module=module, is_class=allow_special_forms) + return arg + else: + _type_convert = typing._type_convert + + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + __class__ = typing._GenericAlias + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + # 3.9 used by __getitem__ below + def copy_with(self, params): + if isinstance(params[-1], _ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + return self.__class__(self.__origin__, params) + + # 3.9; accessed during GenericAlias.__getitem__ when substituting + def __getitem__(self, args): + if self.__origin__ in (Generic, Protocol): + # Can't subscript Generic[...] or Protocol[...]. + raise TypeError(f"Cannot subscript already-subscripted {self}") + if not self.__parameters__: + raise TypeError(f"{self} is not a generic class") + + if not isinstance(args, tuple): + args = (args,) + args = _unpack_args(*(_type_convert(p) for p in args)) + params = self.__parameters__ + for param in params: + prepare = getattr(param, "__typing_prepare_subst__", None) + if prepare is not None: + args = prepare(self, args) + # 3.9 & typing.ParamSpec + elif isinstance(param, ParamSpec): + i = params.index(param) + if ( + i == len(args) + and getattr(param, '__default__', NoDefault) is not NoDefault + ): + args = [*args, param.__default__] + if i >= len(args): + raise TypeError(f"Too few arguments for {self}") + # Special case for Z[[int, str, bool]] == Z[int, str, bool] + if len(params) == 1 and not _is_param_expr(args[0]): + assert i == 0 + args = (args,) + elif ( + isinstance(args[i], list) + # 3.9 + # This class inherits from list do not convert + and not isinstance(args[i], _ConcatenateGenericAlias) + ): + args = (*args[:i], tuple(args[i]), *args[i + 1:]) + + alen = len(args) + plen = len(params) + if alen != plen: + raise TypeError( + f"Too {'many' if alen > plen else 'few'} arguments for {self};" + f" actual {alen}, expected {plen}" + ) + + subst = dict(zip(self.__parameters__, args)) + # determine new args + new_args = [] + for arg in self.__args__: + if isinstance(arg, type): + new_args.append(arg) + continue + if isinstance(arg, TypeVar): + arg = subst[arg] + if ( + (isinstance(arg, typing._GenericAlias) and _is_unpack(arg)) + or ( + hasattr(_types, "GenericAlias") + and isinstance(arg, _types.GenericAlias) + and getattr(arg, "__unpacked__", False) + ) + ): + raise TypeError(f"{arg} is not valid as type argument") + + elif isinstance(arg, + typing._GenericAlias + if not hasattr(_types, "GenericAlias") else + (typing._GenericAlias, _types.GenericAlias) + ): + subparams = arg.__parameters__ + if subparams: + subargs = tuple(subst[x] for x in subparams) + arg = arg[subargs] + new_args.append(arg) + return self.copy_with(tuple(new_args)) + +# 3.10+ +else: + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias + + # 3.10 + if sys.version_info < (3, 11): + + class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True): + # needed for checks in collections.abc.Callable to accept this class + __module__ = "typing" + + def copy_with(self, params): + if isinstance(params[-1], (list, tuple)): + return (*params[:-1], *params[-1]) + if isinstance(params[-1], typing._ConcatenateGenericAlias): + params = (*params[:-1], *params[-1].__args__) + elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + return super(typing._ConcatenateGenericAlias, self).copy_with(params) + + def __getitem__(self, args): + value = super().__getitem__(args) + if isinstance(value, tuple) and any(_is_unpack(t) for t in value): + return tuple(_unpack_args(*(n for n in value))) + return value + + +# 3.9.2 +class _EllipsisDummy: ... + + +# <=3.10 +def _create_concatenate_alias(origin, parameters): + if parameters[-1] is ... and sys.version_info < (3, 9, 2): + # Hack: Arguments must be types, replace it with one. + parameters = (*parameters[:-1], _EllipsisDummy) + if sys.version_info >= (3, 10, 3): + concatenate = _ConcatenateGenericAlias(origin, parameters, + _typevar_types=(TypeVar, ParamSpec), + _paramspec_tvars=True) + else: + concatenate = _ConcatenateGenericAlias(origin, parameters) + if parameters[-1] is not _EllipsisDummy: + return concatenate + # Remove dummy again + concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ... + for p in concatenate.__args__) + if sys.version_info < (3, 10): + # backport needs __args__ adjustment only + return concatenate + concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__ + if p is not _EllipsisDummy) + return concatenate + + +# <=3.10 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable or ellipsis.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]), + parameters[-1]) + return _create_concatenate_alias(self, parameters) + + +# 3.11+; Concatenate does not accept ellipsis in 3.10 +# Breakpoint: https://github.com/python/cpython/pull/30969 +if sys.version_info >= (3, 11): + Concatenate = typing.Concatenate +# <=3.10 +else: + @_ExtensionsSpecialForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) + + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +else: + @_ExtensionsSpecialForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +# 3.13+ +if hasattr(typing, 'TypeIs'): + TypeIs = typing.TypeIs +# <=3.12 +else: + @_ExtensionsSpecialForm + def TypeIs(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type narrower function. ``TypeIs`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeIs[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeIs`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the intersection of the type inside ``TypeIs`` and the argument's + previously known type. + + For example:: + + def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]: + return hasattr(val, '__await__') + + def f(val: Union[int, Awaitable[int]]) -> int: + if is_awaitable(val): + assert_type(val, Awaitable[int]) + else: + assert_type(val, int) + + ``TypeIs`` also works with type variables. For more information, see + PEP 742 (Narrowing types with TypeIs). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +# 3.14+? +if hasattr(typing, 'TypeForm'): + TypeForm = typing.TypeForm +# <=3.13 +else: + class _TypeFormForm(_ExtensionsSpecialForm, _root=True): + # TypeForm(X) is equivalent to X but indicates to the type checker + # that the object is a TypeForm. + def __call__(self, obj, /): + return obj + + @_TypeFormForm + def TypeForm(self, parameters): + """A special form representing the value that results from the evaluation + of a type expression. This value encodes the information supplied in the + type expression, and it represents the type described by that type expression. + + When used in a type expression, TypeForm describes a set of type form objects. + It accepts a single type argument, which must be a valid type expression. + ``TypeForm[T]`` describes the set of all type form objects that represent + the type T or types that are assignable to T. + + Usage: + + def cast[T](typ: TypeForm[T], value: Any) -> T: ... + + reveal_type(cast(int, "x")) # int + + See PEP 747 for more information. + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + + + +if hasattr(typing, "LiteralString"): # 3.11+ + LiteralString = typing.LiteralString +else: + @_SpecialForm + def LiteralString(self, params): + """Represents an arbitrary literal string. + + Example:: + + from bb._vendor.typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Self"): # 3.11+ + Self = typing.Self +else: + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Never"): # 3.11+ + Never = typing.Never +else: + @_SpecialForm + def Never(self, params): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from bb._vendor.typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, 'Required'): # 3.11+ + Required = typing.Required + NotRequired = typing.NotRequired +else: # <=3.10 + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +if hasattr(typing, 'ReadOnly'): + ReadOnly = typing.ReadOnly +else: # <=3.12 + @_ExtensionsSpecialForm + def ReadOnly(self, parameters): + """A special typing construct to mark an item of a TypedDict as read-only. + + For example: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + +_UNPACK_DOC = """\ +Type unpack operator. + +The type unpack operator takes the child types from some container type, +such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For +example: + + # For some generic class `Foo`: + Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str] + + Ts = TypeVarTuple('Ts') + # Specifies that `Bar` is generic in an arbitrary number of types. + # (Think of `Ts` as a tuple of an arbitrary number of individual + # `TypeVar`s, which the `Unpack` is 'pulling out' directly into the + # `Generic[]`.) + class Bar(Generic[Unpack[Ts]]): ... + Bar[int] # Valid + Bar[int, str] # Also valid + +From Python 3.11, this can also be done using the `*` operator: + + Foo[*tuple[int, str]] + class Bar(Generic[*Ts]): ... + +The operator can also be used along with a `TypedDict` to annotate +`**kwargs` in a function signature. For instance: + + class Movie(TypedDict): + name: str + year: int + + # This function expects two keyword arguments - *name* of type `str` and + # *year* of type `int`. + def foo(**kwargs: Unpack[Movie]): ... + +Note that there is only some runtime checking of this operator. Not +everything the runtime allows may be accepted by static type checkers. + +For more information, see PEP 646 and PEP 692. +""" + + +# PEP 692 changed the repr of Unpack[] +# Breakpoint: https://github.com/python/cpython/pull/104048 +if sys.version_info >= (3, 12): + Unpack = typing.Unpack + + def _is_unpack(obj): + return get_origin(obj) is Unpack + +else: # <=3.11 + class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True): + def __init__(self, getitem): + super().__init__(getitem) + self.__doc__ = _UNPACK_DOC + + class _UnpackAlias(typing._GenericAlias, _root=True): + if sys.version_info < (3, 11): + # needed for compatibility with Generic[Unpack[Ts]] + __class__ = typing.TypeVar + + @property + def __typing_unpacked_tuple_args__(self): + assert self.__origin__ is Unpack + assert len(self.__args__) == 1 + arg, = self.__args__ + if isinstance(arg, (typing._GenericAlias, _types.GenericAlias)): + if arg.__origin__ is not tuple: + raise TypeError("Unpack[...] must be used with a tuple type") + return arg.__args__ + return None + + @property + def __typing_is_unpacked_typevartuple__(self): + assert self.__origin__ is Unpack + assert len(self.__args__) == 1 + return isinstance(self.__args__[0], TypeVarTuple) + + def __getitem__(self, args): + if self.__typing_is_unpacked_typevartuple__: + return args + return super().__getitem__(args) + + @_UnpackSpecialForm + def Unpack(self, parameters): + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + + +def _unpack_args(*args): + newargs = [] + for arg in args: + subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) + if subargs is not None and (not (subargs and subargs[-1] is ...)): + newargs.extend(subargs) + else: + newargs.append(arg) + return newargs + + +if _PEP_696_IMPLEMENTED: + from typing import TypeVarTuple + +elif hasattr(typing, "TypeVarTuple"): # 3.11+ + + # Add default parameter - PEP 696 + class TypeVarTuple(metaclass=_TypeVarLikeMeta): + """Type variable tuple.""" + + _backported_typevarlike = typing.TypeVarTuple + + def __new__(cls, name, *, default=NoDefault): + tvt = typing.TypeVarTuple(name) + _set_default(tvt, default) + _set_module(tvt) + + def _typevartuple_prepare_subst(alias, args): + params = alias.__parameters__ + typevartuple_index = params.index(tvt) + for param in params[typevartuple_index + 1:]: + if isinstance(param, TypeVarTuple): + raise TypeError( + f"More than one TypeVarTuple parameter in {alias}" + ) + + alen = len(args) + plen = len(params) + left = typevartuple_index + right = plen - typevartuple_index - 1 + var_tuple_index = None + fillarg = None + for k, arg in enumerate(args): + if not isinstance(arg, type): + subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) + if subargs and len(subargs) == 2 and subargs[-1] is ...: + if var_tuple_index is not None: + raise TypeError( + "More than one unpacked " + "arbitrary-length tuple argument" + ) + var_tuple_index = k + fillarg = subargs[0] + if var_tuple_index is not None: + left = min(left, var_tuple_index) + right = min(right, alen - var_tuple_index - 1) + elif left + right > alen: + raise TypeError(f"Too few arguments for {alias};" + f" actual {alen}, expected at least {plen - 1}") + if left == alen - right and tvt.has_default(): + replacement = _unpack_args(tvt.__default__) + else: + replacement = args[left: alen - right] + + return ( + *args[:left], + *([fillarg] * (typevartuple_index - left)), + replacement, + *([fillarg] * (plen - right - left - typevartuple_index - 1)), + *args[alen - right:], + ) + + tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst + return tvt + + def __init_subclass__(self, *args, **kwds): + raise TypeError("Cannot subclass special typing classes") + +else: # <=3.10 + class TypeVarTuple(_DefaultMixin): + """Type variable tuple. + + Usage:: + + Ts = TypeVarTuple('Ts') + + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + class Array(Generic[*Ts]): ... + + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. + + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: + + class Array(Generic[*Ts]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape + + def get_shape(self) -> Tuple[*Ts]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name, *, default=NoDefault): + self.__name__ = name + _DefaultMixin.__init__(self, default) + + # for pickling: + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + return self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + +if hasattr(typing, "reveal_type"): # 3.11+ + reveal_type = typing.reveal_type +else: # <=3.10 + def reveal_type(obj: T, /) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr) + return obj + + +if hasattr(typing, "_ASSERT_NEVER_REPR_MAX_LENGTH"): # 3.11+ + _ASSERT_NEVER_REPR_MAX_LENGTH = typing._ASSERT_NEVER_REPR_MAX_LENGTH +else: # <=3.10 + _ASSERT_NEVER_REPR_MAX_LENGTH = 100 + + +if hasattr(typing, "assert_never"): # 3.11+ + assert_never = typing.assert_never +else: # <=3.10 + def assert_never(arg: Never, /) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + value = repr(arg) + if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH: + value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...' + raise AssertionError(f"Expected code to be unreachable, but got: {value}") + + +# dataclass_transform exists in 3.11 but lacks the frozen_default parameter +# Breakpoint: https://github.com/python/cpython/pull/99958 +if sys.version_info >= (3, 12): # 3.12+ + dataclass_transform = typing.dataclass_transform +else: # <=3.11 + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + frozen_default: bool = False, + field_specifiers: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], + ... + ] = (), + **kwargs: typing.Any, + ) -> typing.Callable[[T], T]: + """Decorator that marks a function, class, or metaclass as providing + dataclass-like behavior. + + Example: + + from bb._vendor.typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``frozen_default`` indicates whether the ``frozen`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. + + At runtime, this decorator records its arguments in the + ``__dataclass_transform__`` attribute on the decorated object. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "frozen_default": frozen_default, + "field_specifiers": field_specifiers, + "kwargs": kwargs, + } + return cls_or_fn + return decorator + + +if hasattr(typing, "override"): # 3.12+ + override = typing.override +else: # <=3.11 + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + def override(arg: _F, /) -> _F: + """Indicate that a method is intended to override a method in a base class. + + Usage: + + class Base: + def method(self) -> None: + pass + + class Child(Base): + @override + def method(self) -> None: + super().method() + + When this decorator is applied to a method, the type checker will + validate that it overrides a method with the same name on a base class. + This helps prevent bugs that may occur when a base class is changed + without an equivalent change to a child class. + + There is no runtime checking of these properties. The decorator + sets the ``__override__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + + See PEP 698 for details. + + """ + try: + arg.__override__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return arg + + +# Python 3.13.3+ contains a fix for the wrapped __new__ +# Breakpoint: https://github.com/python/cpython/pull/132160 +if sys.version_info >= (3, 13, 3): + deprecated = warnings.deprecated +else: + _T = typing.TypeVar("_T") + + class deprecated: + """Indicate that a class, function or overload is deprecated. + + When this decorator is applied to an object, the type checker + will generate a diagnostic on usage of the deprecated object. + + Usage: + + @deprecated("Use B instead") + class A: + pass + + @deprecated("Use g instead") + def f(): + pass + + @overload + @deprecated("int support is deprecated") + def g(x: int) -> int: ... + @overload + def g(x: str) -> int: ... + + The warning specified by *category* will be emitted at runtime + on use of deprecated objects. For functions, that happens on calls; + for classes, on instantiation and on creation of subclasses. + If the *category* is ``None``, no warning is emitted at runtime. + The *stacklevel* determines where the + warning is emitted. If it is ``1`` (the default), the warning + is emitted at the direct caller of the deprecated object; if it + is higher, it is emitted further up the stack. + Static type checker behavior is not affected by the *category* + and *stacklevel* arguments. + + The deprecation message passed to the decorator is saved in the + ``__deprecated__`` attribute on the decorated object. + If applied to an overload, the decorator + must be after the ``@overload`` decorator for the attribute to + exist on the overload as returned by ``get_overloads()``. + + See PEP 702 for details. + + """ + def __init__( + self, + message: str, + /, + *, + category: typing.Optional[typing.Type[Warning]] = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + if not isinstance(message, str): + raise TypeError( + "Expected an object of type str for 'message', not " + f"{type(message).__name__!r}" + ) + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, arg: _T, /) -> _T: + # Make sure the inner functions created below don't + # retain a reference to self. + msg = self.message + category = self.category + stacklevel = self.stacklevel + if category is None: + arg.__deprecated__ = msg + return arg + elif isinstance(arg, type): + import functools + from types import MethodType + + original_new = arg.__new__ + + @functools.wraps(original_new) + def __new__(cls, /, *args, **kwargs): + if cls is arg: + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + if original_new is not object.__new__: + return original_new(cls, *args, **kwargs) + # Mirrors a similar check in object.__new__. + elif cls.__init__ is object.__init__ and (args or kwargs): + raise TypeError(f"{cls.__name__}() takes no arguments") + else: + return original_new(cls) + + arg.__new__ = staticmethod(__new__) + + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python) + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ + + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = classmethod(__init_subclass__) + # Or otherwise, which likely means it's a builtin such as + # object's implementation of __init_subclass__. + else: + @functools.wraps(original_init_subclass) + def __init_subclass__(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return original_init_subclass(*args, **kwargs) + + arg.__init_subclass__ = __init_subclass__ + + arg.__deprecated__ = __new__.__deprecated__ = msg + __init_subclass__.__deprecated__ = msg + return arg + elif callable(arg): + import asyncio.coroutines + import functools + import inspect + + @functools.wraps(arg) + def wrapper(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel + 1) + return arg(*args, **kwargs) + + if asyncio.coroutines.iscoroutinefunction(arg): + # Breakpoint: https://github.com/python/cpython/pull/99247 + if sys.version_info >= (3, 12): + wrapper = inspect.markcoroutinefunction(wrapper) + else: + wrapper._is_coroutine = asyncio.coroutines._is_coroutine + + arg.__deprecated__ = wrapper.__deprecated__ = msg + return wrapper + else: + raise TypeError( + "@deprecated decorator with non-None category must be applied to " + f"a class or callable, not {arg!r}" + ) + +# Breakpoint: https://github.com/python/cpython/pull/23702 +if sys.version_info < (3, 10): + def _is_param_expr(arg): + return arg is ... or isinstance( + arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias) + ) +else: + def _is_param_expr(arg): + return arg is ... or isinstance( + arg, + ( + tuple, + list, + ParamSpec, + _ConcatenateGenericAlias, + typing._ConcatenateGenericAlias, + ), + ) + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + + This gives a nice error message in case of count mismatch. + """ + # If substituting a single ParamSpec with multiple arguments + # we do not check the count + if (inspect.isclass(cls) and issubclass(cls, typing.Generic) + and len(cls.__parameters__) == 1 + and isinstance(cls.__parameters__[0], ParamSpec) + and parameters + and not _is_param_expr(parameters[0]) + ): + # Generic modifies parameters variable, but here we cannot do this + return + + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if ( + getattr(parameters[alen], '__default__', NoDefault) + is not NoDefault + ): + return + + num_default_tv = sum(getattr(p, '__default__', NoDefault) + is not NoDefault for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + # Breakpoint: https://github.com/python/cpython/pull/27515 + things = "arguments" if sys.version_info >= (3, 10) else "parameters" + raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}" + f" for {cls}; actual {alen}, expected {expect_val}") +else: + # Python 3.11+ + + def _check_generic(cls, parameters, elen): + """Check correct count for parameters of a generic cls (internal helper). + + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + alen = len(parameters) + if alen != elen: + expect_val = elen + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + + # deal with TypeVarLike defaults + # required TypeVarLikes cannot appear after a defaulted one. + if alen < elen: + # since we validate TypeVarLike default in _collect_type_vars + # or _collect_parameters we can safely check parameters[alen] + if ( + getattr(parameters[alen], '__default__', NoDefault) + is not NoDefault + ): + return + + num_default_tv = sum(getattr(p, '__default__', NoDefault) + is not NoDefault for p in parameters) + + elen -= num_default_tv + + expect_val = f"at least {elen}" + + raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments" + f" for {cls}; actual {alen}, expected {expect_val}") + +if not _PEP_696_IMPLEMENTED: + typing._check_generic = _check_generic + + +def _has_generic_or_protocol_as_origin() -> bool: + try: + frame = sys._getframe(2) + # - Catch AttributeError: not all Python implementations have sys._getframe() + # - Catch ValueError: maybe we're called from an unexpected module + # and the call stack isn't deep enough + except (AttributeError, ValueError): + return False # err on the side of leniency + else: + # If we somehow get invoked from outside typing.py, + # also err on the side of leniency + if frame.f_globals.get("__name__") != "typing": + return False + origin = frame.f_locals.get("origin") + # Cannot use "in" because origin may be an object with a buggy __eq__ that + # throws an error. + return origin is typing.Generic or origin is Protocol or origin is typing.Protocol + + +_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)} + + +def _is_unpacked_typevartuple(x) -> bool: + if get_origin(x) is not Unpack: + return False + args = get_args(x) + return ( + bool(args) + and len(args) == 1 + and type(args[0]) in _TYPEVARTUPLE_TYPES + ) + + +# Python 3.11+ _collect_type_vars was renamed to _collect_parameters +if hasattr(typing, '_collect_type_vars'): + def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + + # A required TypeVarLike cannot appear after a TypeVarLike with a default + # if it was a direct call to `Generic[]` or `Protocol[]` + enforce_default_ordering = _has_generic_or_protocol_as_origin() + default_encountered = False + + # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple + type_var_tuple_encountered = False + + for t in types: + if _is_unpacked_typevartuple(t): + type_var_tuple_encountered = True + elif ( + isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias) + and t not in tvars + ): + if enforce_default_ordering: + has_default = getattr(t, '__default__', NoDefault) is not NoDefault + if has_default: + if type_var_tuple_encountered: + raise TypeError('Type parameter with a default' + ' follows TypeVarTuple') + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + elif isinstance(t, tuple): + # Collect nested type_vars + # tuple wrapped by _prepare_paramspec_params(cls, params) + for x in t: + for collected in _collect_type_vars([x]): + if collected not in tvars: + tvars.append(collected) + return tuple(tvars) + + typing._collect_type_vars = _collect_type_vars +else: + def _collect_parameters(args): + """Collect all type variables and parameter specifications in args + in order of first appearance (lexicographic order). + + For example:: + + assert _collect_parameters((T, Callable[P, T])) == (T, P) + """ + parameters = [] + + # A required TypeVarLike cannot appear after a TypeVarLike with default + # if it was a direct call to `Generic[]` or `Protocol[]` + enforce_default_ordering = _has_generic_or_protocol_as_origin() + default_encountered = False + + # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple + type_var_tuple_encountered = False + + for t in args: + if isinstance(t, type): + # We don't want __parameters__ descriptor of a bare Python class. + pass + elif isinstance(t, tuple): + # `t` might be a tuple, when `ParamSpec` is substituted with + # `[T, int]`, or `[int, *Ts]`, etc. + for x in t: + for collected in _collect_parameters([x]): + if collected not in parameters: + parameters.append(collected) + elif hasattr(t, '__typing_subst__'): + if t not in parameters: + if enforce_default_ordering: + has_default = ( + getattr(t, '__default__', NoDefault) is not NoDefault + ) + + if type_var_tuple_encountered and has_default: + raise TypeError('Type parameter with a default' + ' follows TypeVarTuple') + + if has_default: + default_encountered = True + elif default_encountered: + raise TypeError(f'Type parameter {t!r} without a default' + ' follows type parameter with a default') + + parameters.append(t) + else: + if _is_unpacked_typevartuple(t): + type_var_tuple_encountered = True + for x in getattr(t, '__parameters__', ()): + if x not in parameters: + parameters.append(x) + + return tuple(parameters) + + if not _PEP_696_IMPLEMENTED: + typing._collect_parameters = _collect_parameters + +# Backport typing.NamedTuple as it exists in Python 3.13. +# In 3.11, the ability to define generic `NamedTuple`s was supported. +# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. +# On 3.12, we added __orig_bases__ to call-based NamedTuples +# On 3.13, we deprecated kwargs-based NamedTuples +# Breakpoint: https://github.com/python/cpython/pull/105609 +if sys.version_info >= (3, 13): + NamedTuple = typing.NamedTuple +else: + def _make_nmtuple(name, types, module, defaults=()): + fields = [n for n, t in types] + annotations = {n: typing._type_check(t, f"field {n} annotation must be a type") + for n, t in types} + nm_tpl = collections.namedtuple(name, fields, + defaults=defaults, module=module) + nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations + return nm_tpl + + _prohibited_namedtuple_fields = typing._prohibited + _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'}) + + class _NamedTupleMeta(type): + def __new__(cls, typename, bases, ns): + assert _NamedTuple in bases + for base in bases: + if base is not _NamedTuple and base is not typing.Generic: + raise TypeError( + 'can only inherit from a NamedTuple type and Generic') + bases = tuple(tuple if base is _NamedTuple else base for base in bases) + if "__annotations__" in ns: + types = ns["__annotations__"] + elif "__annotate__" in ns: + # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated + types = ns["__annotate__"](1) + else: + types = {} + default_names = [] + for field_name in types: + if field_name in ns: + default_names.append(field_name) + elif default_names: + raise TypeError(f"Non-default namedtuple field {field_name} " + f"cannot follow default field" + f"{'s' if len(default_names) > 1 else ''} " + f"{', '.join(default_names)}") + nm_tpl = _make_nmtuple( + typename, types.items(), + defaults=[ns[n] for n in default_names], + module=ns['__module__'] + ) + nm_tpl.__bases__ = bases + if typing.Generic in bases: + if hasattr(typing, '_generic_class_getitem'): # 3.12+ + nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem) + else: + class_getitem = typing.Generic.__class_getitem__.__func__ + nm_tpl.__class_getitem__ = classmethod(class_getitem) + # update from user namespace without overriding special namedtuple attributes + for key, val in ns.items(): + if key in _prohibited_namedtuple_fields: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special_namedtuple_fields: + if key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + try: + set_name = type(val).__set_name__ + except AttributeError: + pass + else: + try: + set_name(val, nm_tpl, key) + except BaseException as e: + msg = ( + f"Error calling __set_name__ on {type(val).__name__!r} " + f"instance {key!r} in {typename!r}" + ) + # BaseException.add_note() existed on py311, + # but the __set_name__ machinery didn't start + # using add_note() until py312. + # Making sure exceptions are raised in the same way + # as in "normal" classes seems most important here. + # Breakpoint: https://github.com/python/cpython/pull/95915 + if sys.version_info >= (3, 12): + e.add_note(msg) + raise + else: + raise RuntimeError(msg) from e + + if typing.Generic in bases: + nm_tpl.__init_subclass__() + return nm_tpl + + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) + + def _namedtuple_mro_entries(bases): + assert NamedTuple in bases + return (_NamedTuple,) + + def NamedTuple(typename, fields=_marker, /, **kwargs): + """Typed version of namedtuple. + + Usage:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has an extra __annotations__ attribute, giving a + dict that maps field names to types. (The field names are also in + the _fields attribute, which is part of the namedtuple API.) + An alternative equivalent functional syntax is also accepted:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + if fields is _marker: + if kwargs: + deprecated_thing = "Creating NamedTuple classes using keyword arguments" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "Use the class-based or functional syntax instead." + ) + else: + deprecated_thing = "Failing to pass a value for the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif fields is None: + if kwargs: + raise TypeError( + "Cannot pass `None` as the 'fields' parameter " + "and also specify fields using keyword arguments" + ) + else: + deprecated_thing = "Passing `None` as the 'fields' parameter" + example = f"`{typename} = NamedTuple({typename!r}, [])`" + deprecation_msg = ( + "{name} is deprecated and will be disallowed in Python {remove}. " + "To create a NamedTuple class with 0 fields " + "using the functional syntax, " + "pass an empty list, e.g. " + ) + example + "." + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + if fields is _marker or fields is None: + warnings.warn( + deprecation_msg.format(name=deprecated_thing, remove="3.15"), + DeprecationWarning, + stacklevel=2, + ) + fields = kwargs.items() + nt = _make_nmtuple(typename, fields, module=_caller()) + nt.__orig_bases__ = (NamedTuple,) + return nt + + NamedTuple.__mro_entries__ = _namedtuple_mro_entries + + +if hasattr(collections.abc, "Buffer"): + Buffer = collections.abc.Buffer +else: + class Buffer(abc.ABC): # noqa: B024 + """Base class for classes that implement the buffer protocol. + + The buffer protocol allows Python objects to expose a low-level + memory buffer interface. Before Python 3.12, it is not possible + to implement the buffer protocol in pure Python code, or even + to check whether a class implements the buffer protocol. In + Python 3.12 and higher, the ``__buffer__`` method allows access + to the buffer protocol from Python code, and the + ``collections.abc.Buffer`` ABC allows checking whether a class + implements the buffer protocol. + + To indicate support for the buffer protocol in earlier versions, + inherit from this ABC, either in a stub file or at runtime, + or use ABC registration. This ABC provides no methods, because + there is no Python-accessible methods shared by pre-3.12 buffer + classes. It is useful primarily for static checks. + + """ + + # As a courtesy, register the most common stdlib buffer classes. + Buffer.register(memoryview) + Buffer.register(bytearray) + Buffer.register(bytes) + + +# Backport of types.get_original_bases, available on 3.12+ in CPython +if hasattr(_types, "get_original_bases"): + get_original_bases = _types.get_original_bases +else: + def get_original_bases(cls, /): + """Return the class's "original" bases prior to modification by `__mro_entries__`. + + Examples:: + + from typing import TypeVar, Generic + from bb._vendor.typing_extensions import NamedTuple, TypedDict + + T = TypeVar("T") + class Foo(Generic[T]): ... + class Bar(Foo[int], float): ... + class Baz(list[str]): ... + Eggs = NamedTuple("Eggs", [("a", int), ("b", str)]) + Spam = TypedDict("Spam", {"a": int, "b": str}) + + assert get_original_bases(Bar) == (Foo[int], float) + assert get_original_bases(Baz) == (list[str],) + assert get_original_bases(Eggs) == (NamedTuple,) + assert get_original_bases(Spam) == (TypedDict,) + assert get_original_bases(int) == (object,) + """ + try: + return cls.__dict__.get("__orig_bases__", cls.__bases__) + except AttributeError: + raise TypeError( + f'Expected an instance of type, not {type(cls).__name__!r}' + ) from None + + +# NewType is a class on Python 3.10+, making it pickleable +# The error message for subclassing instances of NewType was improved on 3.11+ +# Breakpoint: https://github.com/python/cpython/pull/30268 +if sys.version_info >= (3, 11): + NewType = typing.NewType +else: + class NewType: + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy callable that simply returns its argument. Usage:: + UserId = NewType('UserId', int) + def name_by_id(user_id: UserId) -> str: + ... + UserId('user') # Fails type check + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + num = UserId(5) + 1 # type: int + """ + + def __call__(self, obj, /): + return obj + + def __init__(self, name, tp): + self.__qualname__ = name + if '.' in name: + name = name.rpartition('.')[-1] + self.__name__ = name + self.__supertype__ = tp + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __mro_entries__(self, bases): + # We defined __mro_entries__ to get a better error message + # if a user attempts to subclass a NewType instance. bpo-46170 + supercls_name = self.__name__ + + class Dummy: + def __init_subclass__(cls): + subcls_name = cls.__name__ + raise TypeError( + f"Cannot subclass an instance of NewType. " + f"Perhaps you were looking for: " + f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`" + ) + + return (Dummy,) + + def __repr__(self): + return f'{self.__module__}.{self.__qualname__}' + + def __reduce__(self): + return self.__qualname__ + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + # PEP 604 methods + # It doesn't make sense to have these methods on Python <3.10 + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + +# Breakpoint: https://github.com/python/cpython/pull/124795 +if sys.version_info >= (3, 14): + TypeAliasType = typing.TypeAliasType +# <=3.13 +else: + # Breakpoint: https://github.com/python/cpython/pull/103764 + if sys.version_info >= (3, 12): + # 3.12-3.13 + def _is_unionable(obj): + """Corresponds to is_unionable() in unionobject.c in CPython.""" + return obj is None or isinstance(obj, ( + type, + _types.GenericAlias, + _types.UnionType, + typing.TypeAliasType, + TypeAliasType, + )) + else: + # <=3.11 + def _is_unionable(obj): + """Corresponds to is_unionable() in unionobject.c in CPython.""" + return obj is None or isinstance(obj, ( + type, + _types.GenericAlias, + _types.UnionType, + TypeAliasType, + )) + + if sys.version_info < (3, 10): + # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582, + # so that we emulate the behaviour of `types.GenericAlias` + # on the latest versions of CPython + _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({ + "__class__", + "__bases__", + "__origin__", + "__args__", + "__unpacked__", + "__parameters__", + "__typing_unpacked_tuple_args__", + "__mro_entries__", + "__reduce_ex__", + "__reduce__", + "__copy__", + "__deepcopy__", + }) + + class _TypeAliasGenericAlias(typing._GenericAlias, _root=True): + def __getattr__(self, attr): + if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS: + return object.__getattr__(self, attr) + return getattr(self.__origin__, attr) + + + class TypeAliasType: + """Create named, parameterized type aliases. + + This provides a backport of the new `type` statement in Python 3.12: + + type ListOrSet[T] = list[T] | set[T] + + is equivalent to: + + T = TypeVar("T") + ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,)) + + The name ListOrSet can then be used as an alias for the type it refers to. + + The type_params argument should contain all the type parameters used + in the value of the type alias. If the alias is not generic, this + argument is omitted. + + Static type checkers should only support type aliases declared using + TypeAliasType that follow these rules: + + - The first argument (the name) must be a string literal. + - The TypeAliasType instance must be immediately assigned to a variable + of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid, + as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)'). + + """ + + def __init__(self, name: str, value, *, type_params=()): + if not isinstance(name, str): + raise TypeError("TypeAliasType name must be a string") + if not isinstance(type_params, tuple): + raise TypeError("type_params must be a tuple") + self.__value__ = value + self.__type_params__ = type_params + + default_value_encountered = False + parameters = [] + for type_param in type_params: + if ( + not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec)) + # <=3.11 + # Unpack Backport passes isinstance(type_param, TypeVar) + or _is_unpack(type_param) + ): + raise TypeError(f"Expected a type param, got {type_param!r}") + has_default = ( + getattr(type_param, '__default__', NoDefault) is not NoDefault + ) + if default_value_encountered and not has_default: + raise TypeError(f"non-default type parameter '{type_param!r}'" + " follows default type parameter") + if has_default: + default_value_encountered = True + if isinstance(type_param, TypeVarTuple): + parameters.extend(type_param) + else: + parameters.append(type_param) + self.__parameters__ = tuple(parameters) + def_mod = _caller() + if def_mod != 'typing_extensions': + self.__module__ = def_mod + # Setting this attribute closes the TypeAliasType from further modification + self.__name__ = name + + def __setattr__(self, name: str, value: object, /) -> None: + if hasattr(self, "__name__"): + self._raise_attribute_error(name) + super().__setattr__(name, value) + + def __delattr__(self, name: str, /) -> Never: + self._raise_attribute_error(name) + + def _raise_attribute_error(self, name: str) -> Never: + # Match the Python 3.12 error messages exactly + if name == "__name__": + raise AttributeError("readonly attribute") + elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}: + raise AttributeError( + f"attribute '{name}' of 'typing.TypeAliasType' objects " + "is not writable" + ) + else: + raise AttributeError( + f"'typing.TypeAliasType' object has no attribute '{name}'" + ) + + def __repr__(self) -> str: + return self.__name__ + + if sys.version_info < (3, 11): + def _check_single_param(self, param, recursion=0): + # Allow [], [int], [int, str], [int, ...], [int, T] + if param is ...: + return ... + if param is None: + return None + # Note in <= 3.9 _ConcatenateGenericAlias inherits from list + if isinstance(param, list) and recursion == 0: + return [self._check_single_param(arg, recursion+1) + for arg in param] + return typing._type_check( + param, f'Subscripting {self.__name__} requires a type.' + ) + + def _check_parameters(self, parameters): + if sys.version_info < (3, 11): + return tuple( + self._check_single_param(item) + for item in parameters + ) + return tuple(typing._type_check( + item, f'Subscripting {self.__name__} requires a type.' + ) + for item in parameters + ) + + def __getitem__(self, parameters): + if not self.__type_params__: + raise TypeError("Only generic type aliases are subscriptable") + if not isinstance(parameters, tuple): + parameters = (parameters,) + # Using 3.9 here will create problems with Concatenate + if sys.version_info >= (3, 10): + return _types.GenericAlias(self, parameters) + type_vars = _collect_type_vars(parameters) + parameters = self._check_parameters(parameters) + alias = _TypeAliasGenericAlias(self, parameters) + # alias.__parameters__ is not complete if Concatenate is present + # as it is converted to a list from which no parameters are extracted. + if alias.__parameters__ != type_vars: + alias.__parameters__ = type_vars + return alias + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + "type 'typing_extensions.TypeAliasType' is not an acceptable base type" + ) + + # The presence of this method convinces typing._type_check + # that TypeAliasTypes are types. + def __call__(self): + raise TypeError("Type alias is not callable") + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + def __or__(self, right): + # For forward compatibility with 3.12, reject Unions + # that are not accepted by the built-in Union. + if not _is_unionable(right): + return NotImplemented + return typing.Union[self, right] + + def __ror__(self, left): + if not _is_unionable(left): + return NotImplemented + return typing.Union[left, self] + + +if hasattr(typing, "is_protocol"): + is_protocol = typing.is_protocol + get_protocol_members = typing.get_protocol_members +else: + def is_protocol(tp: type, /) -> bool: + """Return True if the given type is a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, is_protocol + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> is_protocol(P) + True + >>> is_protocol(int) + False + """ + return ( + isinstance(tp, type) + and getattr(tp, '_is_protocol', False) + and tp is not Protocol + and tp is not typing.Protocol + ) + + def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]: + """Return the set of members defined in a Protocol. + + Example:: + + >>> from typing_extensions import Protocol, get_protocol_members + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> get_protocol_members(P) + frozenset({'a', 'b'}) + + Raise a TypeError for arguments that are not Protocols. + """ + if not is_protocol(tp): + raise TypeError(f'{tp!r} is not a Protocol') + if hasattr(tp, '__protocol_attrs__'): + return frozenset(tp.__protocol_attrs__) + return frozenset(_get_protocol_attrs(tp)) + + +if hasattr(typing, "Doc"): + Doc = typing.Doc +else: + class Doc: + """Define the documentation of a type annotation using ``Annotated``, to be + used in class attributes, function and method parameters, return values, + and variables. + + The value should be a positional-only string literal to allow static tools + like editors and documentation generators to use it. + + This complements docstrings. + + The string value passed is available in the attribute ``documentation``. + + Example:: + + >>> from typing_extensions import Annotated, Doc + >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ... + """ + def __init__(self, documentation: str, /) -> None: + self.documentation = documentation + + def __repr__(self) -> str: + return f"Doc({self.documentation!r})" + + def __hash__(self) -> int: + return hash(self.documentation) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Doc): + return NotImplemented + return self.documentation == other.documentation + + +_CapsuleType = getattr(_types, "CapsuleType", None) + +if _CapsuleType is None: + try: + import _socket + except ImportError: + pass + else: + _CAPI = getattr(_socket, "CAPI", None) + if _CAPI is not None: + _CapsuleType = type(_CAPI) + +if _CapsuleType is not None: + CapsuleType = _CapsuleType + __all__.append("CapsuleType") + + +if sys.version_info >= (3, 14): + from annotationlib import Format, get_annotations +else: + # Available since Python 3.14.0a3 + # PR: https://github.com/python/cpython/pull/124415 + class Format(enum.IntEnum): + VALUE = 1 + VALUE_WITH_FAKE_GLOBALS = 2 + FORWARDREF = 3 + STRING = 4 + + # Available since Python 3.14.0a1 + # PR: https://github.com/python/cpython/pull/119891 + def get_annotations(obj, *, globals=None, locals=None, eval_str=False, + format=Format.VALUE): + """Compute the annotations dict for an object. + + obj may be a callable, class, or module. + Passing in an object of any other type raises TypeError. + + Returns a dict. get_annotations() returns a new dict every time + it's called; calling it twice on the same object will return two + different but equivalent dicts. + + This is a backport of `inspect.get_annotations`, which has been + in the standard library since Python 3.10. See the standard library + documentation for more: + + https://docs.python.org/3/library/inspect.html#inspect.get_annotations + + This backport adds the *format* argument introduced by PEP 649. The + three formats supported are: + * VALUE: the annotations are returned as-is. This is the default and + it is compatible with the behavior on previous Python versions. + * FORWARDREF: return annotations as-is if possible, but replace any + undefined names with ForwardRef objects. The implementation proposed by + PEP 649 relies on language changes that cannot be backported; the + typing-extensions implementation simply returns the same result as VALUE. + * STRING: return annotations as strings, in a format close to the original + source. Again, this behavior cannot be replicated directly in a backport. + As an approximation, typing-extensions retrieves the annotations under + VALUE semantics and then stringifies them. + + The purpose of this backport is to allow users who would like to use + FORWARDREF or STRING semantics once PEP 649 is implemented, but who also + want to support earlier Python versions, to simply write: + + typing_extensions.get_annotations(obj, format=Format.FORWARDREF) + + """ + format = Format(format) + if format is Format.VALUE_WITH_FAKE_GLOBALS: + raise ValueError( + "The VALUE_WITH_FAKE_GLOBALS format is for internal use only" + ) + + if eval_str and format is not Format.VALUE: + raise ValueError("eval_str=True is only supported with format=Format.VALUE") + + if isinstance(obj, type): + # class + obj_dict = getattr(obj, '__dict__', None) + if obj_dict and hasattr(obj_dict, 'get'): + ann = obj_dict.get('__annotations__', None) + if isinstance(ann, _types.GetSetDescriptorType): + ann = None + else: + ann = None + + obj_globals = None + module_name = getattr(obj, '__module__', None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + obj_globals = getattr(module, '__dict__', None) + obj_locals = dict(vars(obj)) + unwrap = obj + elif isinstance(obj, _types.ModuleType): + # module + ann = getattr(obj, '__annotations__', None) + obj_globals = obj.__dict__ + obj_locals = None + unwrap = None + elif callable(obj): + # this includes types.Function, types.BuiltinFunctionType, + # types.BuiltinMethodType, functools.partial, functools.singledispatch, + # "class funclike" from Lib/test/test_inspect... on and on it goes. + ann = getattr(obj, '__annotations__', None) + obj_globals = getattr(obj, '__globals__', None) + obj_locals = None + unwrap = obj + elif hasattr(obj, '__annotations__'): + ann = obj.__annotations__ + obj_globals = obj_locals = unwrap = None + else: + raise TypeError(f"{obj!r} is not a module, class, or callable.") + + if ann is None: + return {} + + if not isinstance(ann, dict): + raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None") + + if not ann: + return {} + + if not eval_str: + if format is Format.STRING: + return { + key: value if isinstance(value, str) else typing._type_repr(value) + for key, value in ann.items() + } + return dict(ann) + + if unwrap is not None: + while True: + if hasattr(unwrap, '__wrapped__'): + unwrap = unwrap.__wrapped__ + continue + if isinstance(unwrap, functools.partial): + unwrap = unwrap.func + continue + break + if hasattr(unwrap, "__globals__"): + obj_globals = unwrap.__globals__ + + if globals is None: + globals = obj_globals + if locals is None: + locals = obj_locals or {} + + # "Inject" type parameters into the local namespace + # (unless they are shadowed by assignments *in* the local namespace), + # as a way of emulating annotation scopes when calling `eval()` + if type_params := getattr(obj, "__type_params__", ()): + locals = {param.__name__: param for param in type_params} | locals + + return_value = {key: + value if not isinstance(value, str) else eval(value, globals, locals) + for key, value in ann.items() } + return return_value + + +if hasattr(typing, "evaluate_forward_ref"): + evaluate_forward_ref = typing.evaluate_forward_ref +else: + # Implements annotationlib.ForwardRef.evaluate + def _eval_with_owner( + forward_ref, *, owner=None, globals=None, locals=None, type_params=None + ): + if forward_ref.__forward_evaluated__: + return forward_ref.__forward_value__ + if getattr(forward_ref, "__cell__", None) is not None: + try: + value = forward_ref.__cell__.cell_contents + except ValueError: + pass + else: + forward_ref.__forward_evaluated__ = True + forward_ref.__forward_value__ = value + return value + if owner is None: + owner = getattr(forward_ref, "__owner__", None) + + if ( + globals is None + and getattr(forward_ref, "__forward_module__", None) is not None + ): + globals = getattr( + sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None + ) + if globals is None: + globals = getattr(forward_ref, "__globals__", None) + if globals is None: + if isinstance(owner, type): + module_name = getattr(owner, "__module__", None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + globals = getattr(module, "__dict__", None) + elif isinstance(owner, _types.ModuleType): + globals = getattr(owner, "__dict__", None) + elif callable(owner): + globals = getattr(owner, "__globals__", None) + + # If we pass None to eval() below, the globals of this module are used. + if globals is None: + globals = {} + + if locals is None: + locals = {} + if isinstance(owner, type): + locals.update(vars(owner)) + + if type_params is None and owner is not None: + # "Inject" type parameters into the local namespace + # (unless they are shadowed by assignments *in* the local namespace), + # as a way of emulating annotation scopes when calling `eval()` + type_params = getattr(owner, "__type_params__", None) + + # Type parameters exist in their own scope, which is logically + # between the locals and the globals. We simulate this by adding + # them to the globals. + if type_params is not None: + globals = dict(globals) + for param in type_params: + globals[param.__name__] = param + + arg = forward_ref.__forward_arg__ + if arg.isidentifier() and not keyword.iskeyword(arg): + if arg in locals: + value = locals[arg] + elif arg in globals: + value = globals[arg] + elif hasattr(builtins, arg): + return getattr(builtins, arg) + else: + raise NameError(arg) + else: + code = forward_ref.__forward_code__ + value = eval(code, globals, locals) + forward_ref.__forward_evaluated__ = True + forward_ref.__forward_value__ = value + return value + + def evaluate_forward_ref( + forward_ref, + *, + owner=None, + globals=None, + locals=None, + type_params=None, + format=None, + _recursive_guard=frozenset(), + ): + """Evaluate a forward reference as a type hint. + + This is similar to calling the ForwardRef.evaluate() method, + but unlike that method, evaluate_forward_ref() also: + + * Recursively evaluates forward references nested within the type hint. + * Rejects certain objects that are not valid type hints. + * Replaces type hints that evaluate to None with types.NoneType. + * Supports the *FORWARDREF* and *STRING* formats. + + *forward_ref* must be an instance of ForwardRef. *owner*, if given, + should be the object that holds the annotations that the forward reference + derived from, such as a module, class object, or function. It is used to + infer the namespaces to use for looking up names. *globals* and *locals* + can also be explicitly given to provide the global and local namespaces. + *type_params* is a tuple of type parameters that are in scope when + evaluating the forward reference. This parameter must be provided (though + it may be an empty tuple) if *owner* is not given and the forward reference + does not already have an owner set. *format* specifies the format of the + annotation and is a member of the annotationlib.Format enum. + + """ + if format == Format.STRING: + return forward_ref.__forward_arg__ + if forward_ref.__forward_arg__ in _recursive_guard: + return forward_ref + + # Evaluate the forward reference + try: + value = _eval_with_owner( + forward_ref, + owner=owner, + globals=globals, + locals=locals, + type_params=type_params, + ) + except NameError: + if format == Format.FORWARDREF: + return forward_ref + else: + raise + + if isinstance(value, str): + value = ForwardRef(value) + + # Recursively evaluate the type + if isinstance(value, ForwardRef): + if getattr(value, "__forward_module__", True) is not None: + globals = None + return evaluate_forward_ref( + value, + globals=globals, + locals=locals, + type_params=type_params, owner=owner, + _recursive_guard=_recursive_guard, format=format + ) + if sys.version_info < (3, 12, 5) and type_params: + # Make use of type_params + locals = dict(locals) if locals else {} + for tvar in type_params: + if tvar.__name__ not in locals: # lets not overwrite something present + locals[tvar.__name__] = tvar + if sys.version_info < (3, 12, 5): + return typing._eval_type( + value, + globals, + locals, + recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, + ) + else: + return typing._eval_type( + value, + globals, + locals, + type_params, + recursive_guard=_recursive_guard | {forward_ref.__forward_arg__}, + ) + + +class Sentinel: + """Create a unique sentinel object. + + *name* should be the name of the variable to which the return value shall be assigned. + + *repr*, if supplied, will be used for the repr of the sentinel object. + If not provided, "" will be used. + """ + + def __init__( + self, + name: str, + repr: typing.Optional[str] = None, + ): + self._name = name + self._repr = repr if repr is not None else f'<{name}>' + + def __repr__(self): + return self._repr + + if sys.version_info < (3, 11): + # The presence of this method convinces typing._type_check + # that Sentinels are types. + def __call__(self, *args, **kwargs): + raise TypeError(f"{type(self).__name__!r} object is not callable") + + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __getstate__(self): + raise TypeError(f"Cannot pickle {type(self).__name__!r} object") + + +if sys.version_info >= (3, 14, 0, "beta"): + type_repr = annotationlib.type_repr +else: + def type_repr(value): + """Convert a Python value to a format suitable for use with the STRING format. + + This is intended as a helper for tools that support the STRING format but do + not have access to the code that originally produced the annotations. It uses + repr() for most objects. + + """ + if isinstance(value, (type, _types.FunctionType, _types.BuiltinFunctionType)): + if value.__module__ == "builtins": + return value.__qualname__ + return f"{value.__module__}.{value.__qualname__}" + if value is ...: + return "..." + return repr(value) + + +# Aliases for items that are in typing in all supported versions. +# We use hasattr() checks so this library will continue to import on +# future versions of Python that may remove these names. +_typing_names = [ + "AbstractSet", + "AnyStr", + "BinaryIO", + "Callable", + "Collection", + "Container", + "Dict", + "FrozenSet", + "Hashable", + "IO", + "ItemsView", + "Iterable", + "Iterator", + "KeysView", + "List", + "Mapping", + "MappingView", + "Match", + "MutableMapping", + "MutableSequence", + "MutableSet", + "Optional", + "Pattern", + "Reversible", + "Sequence", + "Set", + "Sized", + "TextIO", + "Tuple", + "Union", + "ValuesView", + "cast", + "no_type_check", + "no_type_check_decorator", + # This is private, but it was defined by typing_extensions for a long time + # and some users rely on it. + "_AnnotatedAlias", +] +globals().update( + {name: getattr(typing, name) for name in _typing_names if hasattr(typing, name)} +) +# These are defined unconditionally because they are used in +# typing-extensions itself. +Generic = typing.Generic +ForwardRef = typing.ForwardRef +Annotated = typing.Annotated diff --git a/lib/bb/_vendor/typing_extensions.pyi b/lib/bb/_vendor/typing_extensions.pyi new file mode 100644 index 000000000..547f7a1b0 --- /dev/null +++ b/lib/bb/_vendor/typing_extensions.pyi @@ -0,0 +1 @@ +from typing_extensions import * \ No newline at end of file From patchwork Wed Jun 24 17:20:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Woolley X-Patchwork-Id: 90877 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 632C5CDB47F for ; Wed, 24 Jun 2026 17:20:42 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.12960.1782321632449200939 for ; Wed, 24 Jun 2026 10:20:32 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=GSdiauPE; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=0635205cc7=rob.woolley@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 65OEegxX2602576 for ; Wed, 24 Jun 2026 10:20:32 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=cc:content-transfer-encoding:content-type:date:from :in-reply-to:message-id:mime-version:references:subject:to; s= PPS06212021; bh=9c/U7jnamgo5eaNZ4JJAHRY/a2/keCC6TXSZGXyC2ME=; b= GSdiauPEAyhAY3X5JgSVFleuAzrosHmnR9ANm2yuU1GpJK2wkul9pjAFxIy/1Whs xl2gyF/KXHOEchBuGp/dBpMDpwLEOTvDRnCkrufaJSJFu8Iql9Zi2bivYXcj+bQo nBDH66kuwd5RqHciFy49vT9lD7HzpzMXtAawfbWzeYlHN/SUyGXhk3xqHQoxAITq m/HmbNyJrrawfImCsH2/zEYbpHCKuhxaW+GLukSzwD+b+iHX6FC1KO6SL8XJ62/Y /x3+ghpL8D8yalaOraDtZ+YfvLSly9hFLHJHsNuBowcpxhAvmFw2KyUaupcHs+to sAYQEuzm+1VNYY5nJnEqbw== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [128.224.246.36]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 4eykj1jntn-11 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Wed, 24 Jun 2026 10:20:31 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (10.11.224.121) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.61; Wed, 24 Jun 2026 10:20:13 -0700 Received: from ala-lpggp3.wrs.com (10.11.232.110) by ala-exchng01.corp.ad.wrs.com (10.11.224.121) with Microsoft SMTP Server id 15.1.2507.61 via Frontend Transport; Wed, 24 Jun 2026 10:20:13 -0700 From: Rob Woolley Date: Wed, 24 Jun 2026 10:20:11 -0700 Subject: [PATCH v4 11/11] bitbake-setup: Add exception for E402 for bb.__version__ MIME-Version: 1.0 Message-ID: <20260624-add-pypi-v8-v4-11-ff499f1fd5a5@windriver.com> References: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> In-Reply-To: <20260624-add-pypi-v8-v4-0-ff499f1fd5a5@windriver.com> To: CC: Rob Woolley X-Mailer: b4 0.15.2 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX+TOb7B6ZC/7H qk8wNEy7gbkRlyhTplPozWyp5fFg8gTO07gHX46mPIzsXWpeq3Y9aEOFTDLcbHZQ6COmXphOXcv w+826/m5BZSOBKMMreyPUmvrpLvhaYKMgjpzizVmj8HWlUFm5yJZQyMg7gWE/ZM74r9obT8gce6 MWWTB4FU8+BfFMIKkmaY8X49inyQJJek1LhqRv0gy+iXsmVDYWtsog3OBfoPYiFNAmaM2l4NIAG TEiu1DxoMEis6i5CiO2Nj7rpYkB0lzqSGI02MfYgbbzXmX+cXeNtUfC0j42kxTOUbIg4eWvKdHO 0qzRvSkn5S2gdodzx1p012y7UfOgRc+gFS6p+X8aqgvv9+Wn95b8AQtGeETklXv6ZpycAiDtLTm NRVBTmcSaHN+8UJDTq/1K5vK7rTOQQ== X-Proofpoint-ORIG-GUID: vzUHnYZnBkCP1mHxzj8JP6YJ-WZ2d5QO X-Proofpoint-Spam-Info: AW1haW4tMjYwNjI0MDE0NSBTYWx0ZWRfX4LEXFzAJ4P0L 83gKaxzydnaxLlipnvH+wTaWxl/LtFejGgRpLGfdTroAU+USjQESWRxFUqsfkHnap74r0uN0i49 /QXmqmu5XPWLF2TZdByhM53zPeImconOzykprF5A04za1F3wHJj8 X-Proofpoint-GUID: vzUHnYZnBkCP1mHxzj8JP6YJ-WZ2d5QO X-Authority-Analysis: v=2.4 cv=JNILdcKb c=1 sm=1 tr=0 ts=6a3c11df cx=c_pps a=AbJuCvi4Y3V6hpbCNWx0WA==:117 a=AbJuCvi4Y3V6hpbCNWx0WA==:17 a=IkcTkHD0fZMA:10 a=FelO9ux0wxsA:10 a=VkNPw1HP01LnGYTKEx00:22 a=bi6dqmuHe4P4UrxVR6um:22 a=HK-ge7EqtdluswH-FwHe:22 a=t7CeM3EgAAAA:8 a=HT31Qwe42N0YAzBbpOIA:9 a=QEXdDO2ut3YA:10 a=FdTzh2GWekK77mhwV6Dw:22 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.125,FMLib:17.12.100.49 definitions=2026-06-24_03,2026-06-24_01,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 adultscore=0 phishscore=0 malwarescore=0 spamscore=0 lowpriorityscore=0 bulkscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2606240145 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 24 Jun 2026 17:20:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/bitbake-devel/message/19777 The ruff linter reported: E402 Module level import not at top of file Add an exception for bb.__version__ to match the other imports that are happening after the modifications to sys.path. Signed-off-by: Rob Woolley --- bin/bitbake-setup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/bitbake-setup b/bin/bitbake-setup index 525187ddf..ce80809e7 100755 --- a/bin/bitbake-setup +++ b/bin/bitbake-setup @@ -28,7 +28,7 @@ sys.path[0:0] = [os.path.join(os.path.dirname(bindir), 'lib')] import bb.msg # noqa: E402 import bb.process # noqa: E402 -from bb import __version__ as libbb_version +from bb import __version__ as libbb_version # noqa: E402 if __name__ == "__main__": if __version__ != libbb_version: