diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33a5950c1483a2..d4397fc7de54a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -278,13 +278,13 @@ jobs: # unsupported as it most resembles other 1.1.1-work-a-like ssl APIs # supported by important vendors such as AWS-LC. - { name: openssl, version: 1.1.1w } - - { name: openssl, version: 3.0.19 } - - { name: openssl, version: 3.3.6 } - - { name: openssl, version: 3.4.4 } - - { name: openssl, version: 3.5.5 } - - { name: openssl, version: 3.6.1 } + - { name: openssl, version: 3.0.20 } + - { name: openssl, version: 3.3.7 } + - { name: openssl, version: 3.4.5 } + - { name: openssl, version: 3.5.6 } + - { name: openssl, version: 3.6.2 } ## AWS-LC - - { name: aws-lc, version: 1.68.0 } + - { name: aws-lc, version: 1.72.1 } env: SSLLIB_VER: ${{ matrix.ssllib.version }} MULTISSL_DIR: ${{ github.workspace }}/multissl @@ -398,7 +398,7 @@ jobs: needs: build-context if: needs.build-context.outputs.run-ubuntu == 'true' env: - OPENSSL_VER: 3.5.5 + OPENSSL_VER: 3.5.6 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -506,7 +506,7 @@ jobs: matrix: os: [ubuntu-24.04] env: - OPENSSL_VER: 3.5.5 + OPENSSL_VER: 3.5.6 PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index 87fba6221fb917..a7e307848af670 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -35,7 +35,7 @@ jobs: runs-on: ${{ inputs.os }} timeout-minutes: 60 env: - OPENSSL_VER: 3.5.5 + OPENSSL_VER: 3.5.6 PYTHONSTRICTEXTENSIONBUILD: 1 TERM: linux steps: diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 17cf57dd00b4bd..f45a22addbb56a 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2156,7 +2156,7 @@ without the dedicated syntax, as documented below. Added support for the ``bound``, ``covariant``, ``contravariant``, and ``infer_variance`` parameters. -.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False, default=typing.NoDefault) +.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False, infer_variance=False, default=typing.NoDefault) Parameter specification variable. A specialized version of :ref:`type variables `. diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index e9810d6bd5d57b..d67cc4dd1b19ab 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -757,22 +757,16 @@ def _is_kw_only(a_type, dataclasses): return a_type is dataclasses.KW_ONLY -def _is_type(annotation, cls, a_module, a_type, is_type_predicate): - # Given a type annotation string, does it refer to a_type in - # a_module? For example, when checking that annotation denotes a - # ClassVar, then a_module is typing, and a_type is - # typing.ClassVar. +def _get_type_from_annotation(annotation, cls): + # Loosely parse a string annotation and return its type. - # It's possible to look up a_module given a_type, but it involves - # looking in sys.modules (again!), and seems like a waste since - # the caller already knows a_module. + # We can't perform a full type hint evaluation at the point where @dataclass + # was invoked because class's module is not fully initialized yet. So we resort + # to parsing string annotation using regexp, and extracting a type before + # the first square bracket. # - annotation is a string type annotation # - cls is the class that this annotation was found in - # - a_module is the module we want to match - # - a_type is the type in that module we want to match - # - is_type_predicate is a function called with (obj, a_module) - # that determines if obj is of the desired type. # Since this test does not do a local namespace lookup (and # instead only a module (global) lookup), there are some things it @@ -803,24 +797,21 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate): # https://github.com/python/cpython/issues/77634 for details. global _MODULE_IDENTIFIER_RE if _MODULE_IDENTIFIER_RE is None: - _MODULE_IDENTIFIER_RE = re.compile(r'(?:\s*(\w+)\s*\.)?\s*(\w+)') + _MODULE_IDENTIFIER_RE = re.compile(r'^\s*(\w+(?:\s*\.\s*\w+)*)') match = _MODULE_IDENTIFIER_RE.prefixmatch(annotation) - if match: - ns = None - module_name = match[1] - if not module_name: - # No module name, assume the class's module did - # "from dataclasses import InitVar". - ns = sys.modules.get(cls.__module__).__dict__ - else: - # Look up module_name in the class's module. - module = sys.modules.get(cls.__module__) - if module and module.__dict__.get(module_name) is a_module: - ns = sys.modules.get(a_type.__module__).__dict__ - if ns and is_type_predicate(ns.get(match[2]), a_module): - return True - return False + if not match: + return None + + # Note: _MODULE_IDENTIFIER_RE guarantees that path is non-empty + path = match[1].split(".") + root = sys.modules.get(cls.__module__) + for path_item in path: + root = getattr(root, path_item.strip(), None) + if root is None: + return None + + return root def _get_field(cls, a_name, a_type, default_kw_only): @@ -858,6 +849,10 @@ def _get_field(cls, a_name, a_type, default_kw_only): # is actually of the correct type. # For the complete discussion, see https://bugs.python.org/issue33453 + if isinstance(a_type, str): + a_type_annotation = _get_type_from_annotation(a_type, cls) + else: + a_type_annotation = a_type # If typing has not been imported, then it's impossible for any # annotation to be a ClassVar. So, only look for ClassVar if @@ -865,10 +860,7 @@ def _get_field(cls, a_name, a_type, default_kw_only): # module). typing = sys.modules.get('typing') if typing: - if (_is_classvar(a_type, typing) - or (isinstance(f.type, str) - and _is_type(f.type, cls, typing, typing.ClassVar, - _is_classvar))): + if _is_classvar(a_type_annotation, typing): f._field_type = _FIELD_CLASSVAR # If the type is InitVar, or if it's a matching string annotation, @@ -877,10 +869,7 @@ def _get_field(cls, a_name, a_type, default_kw_only): # The module we're checking against is the module we're # currently in (dataclasses.py). dataclasses = sys.modules[__name__] - if (_is_initvar(a_type, dataclasses) - or (isinstance(f.type, str) - and _is_type(f.type, cls, dataclasses, dataclasses.InitVar, - _is_initvar))): + if _is_initvar(a_type_annotation, dataclasses): f._field_type = _FIELD_INITVAR # Validations for individual fields. This is delayed until now, @@ -1073,10 +1062,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, dataclasses = sys.modules[__name__] for name, type in cls_annotations.items(): # See if this is a marker to change the value of kw_only. - if (_is_kw_only(type, dataclasses) - or (isinstance(type, str) - and _is_type(type, cls, dataclasses, dataclasses.KW_ONLY, - _is_kw_only))): + if isinstance(type, str): + a_type_annotation = _get_type_from_annotation(type, cls) + else: + a_type_annotation = type + if _is_kw_only(a_type_annotation, dataclasses): # Switch the default to kw_only=True, and ignore this # annotation: it's not a real field. if KW_ONLY_seen: diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 8a0a7d12c04aa4..dcd6a3ef9abfab 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -4353,10 +4353,17 @@ def test_classvar_module_level_import(self): from test.test_dataclasses import dataclass_module_1_str from test.test_dataclasses import dataclass_module_2 from test.test_dataclasses import dataclass_module_2_str - - for m in (dataclass_module_1, dataclass_module_1_str, - dataclass_module_2, dataclass_module_2_str, - ): + from test.test_dataclasses import dataclass_module_3 + from test.test_dataclasses import dataclass_module_3_str + from test.test_dataclasses import dataclass_module_4 + from test.test_dataclasses import dataclass_module_4_str + + for m in ( + dataclass_module_1, dataclass_module_1_str, + dataclass_module_2, dataclass_module_2_str, + dataclass_module_3, dataclass_module_3_str, + dataclass_module_4, dataclass_module_4_str, + ): with self.subTest(m=m): # There's a difference in how the ClassVars are # interpreted when using string annotations or @@ -4660,6 +4667,14 @@ def custom_dataclass(cls, *args, **kwargs): self.assertEqual(c.x, 10) self.assertEqual(c.__custom__, True) + def test_empty_annotation_string(self): + @dataclass + class DataclassWithEmptyTypeAnnotation: + x: "" + + c = DataclassWithEmptyTypeAnnotation(10) + self.assertEqual(c.x, 10) + class TestReplace(unittest.TestCase): def test(self): diff --git a/Lib/test/test_dataclasses/_types_proxy.py b/Lib/test/test_dataclasses/_types_proxy.py new file mode 100644 index 00000000000000..f4aaeef7aec59d --- /dev/null +++ b/Lib/test/test_dataclasses/_types_proxy.py @@ -0,0 +1,8 @@ +# We need this to test a case when a type +# is imported via some other package, +# like ClassVar from typing_extensions instead of typing. +# https://github.com/python/cpython/issues/133956 +from typing import ClassVar +from dataclasses import InitVar + +__all__ = ["ClassVar", "InitVar"] diff --git a/Lib/test/test_dataclasses/dataclass_module_3.py b/Lib/test/test_dataclasses/dataclass_module_3.py new file mode 100644 index 00000000000000..74abc091f35acd --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_3.py @@ -0,0 +1,32 @@ +#from __future__ import annotations +USING_STRINGS = False + +# dataclass_module_3.py and dataclass_module_3_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass +import test.test_dataclasses._types_proxy as tp + +T_CV2 = tp.ClassVar[int] +T_CV3 = tp.ClassVar + +T_IV2 = tp.InitVar[int] +T_IV3 = tp.InitVar + +@dataclass +class CV: + T_CV4 = tp.ClassVar + cv0: tp.ClassVar[int] = 20 + cv1: tp.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = tp.InitVar + iv0: tp.InitVar[int] + iv1: tp.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_3_str.py b/Lib/test/test_dataclasses/dataclass_module_3_str.py new file mode 100644 index 00000000000000..49e5fca61831b6 --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_3_str.py @@ -0,0 +1,32 @@ +from __future__ import annotations +USING_STRINGS = True + +# dataclass_module_3.py and dataclass_module_3_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass +import test.test_dataclasses._types_proxy as tp + +T_CV2 = tp.ClassVar[int] +T_CV3 = tp.ClassVar + +T_IV2 = tp.InitVar[int] +T_IV3 = tp.InitVar + +@dataclass +class CV: + T_CV4 = tp.ClassVar + cv0: tp.ClassVar[int] = 20 + cv1: tp.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = tp.InitVar + iv0: tp.InitVar[int] + iv1: tp.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_4.py b/Lib/test/test_dataclasses/dataclass_module_4.py new file mode 100644 index 00000000000000..7e0c8a18356590 --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_4.py @@ -0,0 +1,38 @@ +#from __future__ import annotations +USING_STRINGS = False + +# dataclass_module_4.py and dataclass_module_4_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass +import dataclasses +import typing + +class TypingProxy: + class Nested: + ClassVar = typing.ClassVar + InitVar = dataclasses.InitVar + +T_CV2 = TypingProxy.Nested.ClassVar[int] +T_CV3 = TypingProxy.Nested.ClassVar + +T_IV2 = TypingProxy.Nested.InitVar[int] +T_IV3 = TypingProxy.Nested.InitVar + +@dataclass +class CV: + T_CV4 = TypingProxy.Nested.ClassVar + cv0: TypingProxy.Nested.ClassVar[int] = 20 + cv1: TypingProxy.Nested.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = TypingProxy.Nested.InitVar + iv0: TypingProxy.Nested.InitVar[int] + iv1: TypingProxy.Nested.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Lib/test/test_dataclasses/dataclass_module_4_str.py b/Lib/test/test_dataclasses/dataclass_module_4_str.py new file mode 100644 index 00000000000000..876f3dcf7c88fa --- /dev/null +++ b/Lib/test/test_dataclasses/dataclass_module_4_str.py @@ -0,0 +1,38 @@ +from __future__ import annotations +USING_STRINGS = True + +# dataclass_module_4.py and dataclass_module_4_str.py are identical +# except only the latter uses string annotations. + +from dataclasses import dataclass +import dataclasses +import typing + +class TypingProxy: + class Nested: + ClassVar = typing.ClassVar + InitVar = dataclasses.InitVar + +T_CV2 = TypingProxy.Nested.ClassVar[int] +T_CV3 = TypingProxy.Nested.ClassVar + +T_IV2 = TypingProxy.Nested.InitVar[int] +T_IV3 = TypingProxy.Nested.InitVar + +@dataclass +class CV: + T_CV4 = TypingProxy.Nested.ClassVar + cv0: TypingProxy.Nested.ClassVar[int] = 20 + cv1: TypingProxy.Nested.ClassVar = 30 + cv2: T_CV2 + cv3: T_CV3 + not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar. + +@dataclass +class IV: + T_IV4 = TypingProxy.Nested.InitVar + iv0: TypingProxy.Nested.InitVar[int] + iv1: TypingProxy.Nested.InitVar + iv2: T_IV2 + iv3: T_IV3 + not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar. diff --git a/Misc/NEWS.d/next/Library/2025-05-16-01-43-58.gh-issue-133956.5kWDYd.rst b/Misc/NEWS.d/next/Library/2025-05-16-01-43-58.gh-issue-133956.5kWDYd.rst new file mode 100644 index 00000000000000..5923e12d55964c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-16-01-43-58.gh-issue-133956.5kWDYd.rst @@ -0,0 +1,4 @@ +Fix bug where :func:`@dataclass ` +wouldn't detect ``ClassVar`` fields +if ``ClassVar`` was re-exported from a module +other than :mod:`typing`. diff --git a/Misc/NEWS.d/next/Windows/2026-05-03-13-55-51.gh-issue-149254.ENtMYD.rst b/Misc/NEWS.d/next/Windows/2026-05-03-13-55-51.gh-issue-149254.ENtMYD.rst new file mode 100644 index 00000000000000..946654700a10c5 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2026-05-03-13-55-51.gh-issue-149254.ENtMYD.rst @@ -0,0 +1 @@ +Updated bundled version of OpenSSL to 3.5.6. diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json index c96367f57fb3f2..593fa01bf25ed1 100644 --- a/Misc/externals.spdx.json +++ b/Misc/externals.spdx.json @@ -70,21 +70,21 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "619b30acf7d9b13c9d0ba90d17349e8b524c380cd23d39334b143f74dc4e5ec9" + "checksumValue": "cf01946f3a61ba45a08c1e35b223d41d23963e3df5ac98cbad6c8fa5a81070ca" } ], - "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/openssl-3.5.5.tar.gz", + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/openssl-3.5.6.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:openssl:openssl:3.5.5:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:openssl:openssl:3.5.6:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], "licenseConcluded": "NOASSERTION", "name": "openssl", "primaryPackagePurpose": "SOURCE", - "versionInfo": "3.5.5" + "versionInfo": "3.5.6" }, { "SPDXID": "SPDXRef-PACKAGE-sqlite", diff --git a/Modules/_ssl_data_36.h b/Modules/_ssl_data_36.h index 5a2e0d067e2dc7..e1c1eb30ff6a7b 100644 --- a/Modules/_ssl_data_36.h +++ b/Modules/_ssl_data_36.h @@ -1,6 +1,6 @@ /* File generated by Tools/ssl/make_ssl_data.py */ -/* Generated on 2026-02-13T18:19:19.227109+00:00 */ -/* Generated from Git commit openssl-3.6.1-0-gc9a9e5b10 */ +/* Generated on 2026-05-03T19:50:43.034653+00:00 */ +/* Generated from Git commit openssl-3.6.2-0-gfe686e15d */ /* generated from args.lib2errnum */ static struct py_ssl_library_code library_codes[] = { @@ -4263,6 +4263,11 @@ static struct py_ssl_error_code error_codes[] = { #else {"CONNECT_FAILURE", 61, 100}, #endif + #ifdef HTTP_R_CONTENT_TYPE_MISMATCH + {"CONTENT_TYPE_MISMATCH", ERR_LIB_HTTP, HTTP_R_CONTENT_TYPE_MISMATCH}, + #else + {"CONTENT_TYPE_MISMATCH", 61, 131}, + #endif #ifdef HTTP_R_ERROR_PARSING_ASN1_LENGTH {"ERROR_PARSING_ASN1_LENGTH", ERR_LIB_HTTP, HTTP_R_ERROR_PARSING_ASN1_LENGTH}, #else diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat index f80a025fb3bc78..405285b65dd270 100644 --- a/PCbuild/get_externals.bat +++ b/PCbuild/get_externals.bat @@ -54,7 +54,7 @@ echo.Fetching external libraries... set libraries= set libraries=%libraries% bzip2-1.0.8 if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4 -if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.5.5 +if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.5.6 set libraries=%libraries% mpdecimal-4.0.0 set libraries=%libraries% sqlite-3.50.4.0 if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.15.0 @@ -79,7 +79,7 @@ echo.Fetching external binaries... set binaries= if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4 -if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.5.5 +if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.5.6 if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.15.0 if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06 if NOT "%IncludeLLVM%"=="false" set binaries=%binaries% llvm-21.1.4.0 diff --git a/PCbuild/python.props b/PCbuild/python.props index 3ad8d81dfc9a95..f29f3d18de5f9d 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -82,8 +82,8 @@ $(libffiDir)$(ArchName)\ $(libffiOutDir)include $(ExternalsDir)\mpdecimal-4.0.0\ - $(ExternalsDir)openssl-3.5.5\ - $(ExternalsDir)openssl-bin-3.5.5\$(ArchName)\ + $(ExternalsDir)openssl-3.5.6\ + $(ExternalsDir)openssl-bin-3.5.6\$(ArchName)\ $(opensslOutDir)include $(ExternalsDir)\nasm-2.11.06\ $(ExternalsDir)\zlib-1.3.1\ diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 48207e5330fa90..6be1a5ae94ebc6 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -49,11 +49,11 @@ ] OPENSSL_RECENT_VERSIONS = [ - "3.0.19", - "3.3.6", - "3.4.4", - "3.5.5", - "3.6.1", + "3.0.20", + "3.3.7", + "3.4.5", + "3.5.6", + "3.6.2", # See make_ssl_data.py for notes on adding a new version. ]