Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Doc/deprecations/pending-removal-in-3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ Pending removal in Python 3.16

* :mod:`logging`:

Support for custom logging handlers with the *strm* argument is deprecated
and scheduled for removal in Python 3.16. Define handlers with the *stream*
argument instead. (Contributed by Mariusz Felisiak in :gh:`115032`.)
* Support for custom logging handlers with the *strm* argument is deprecated
and scheduled for removal in Python 3.16. Define handlers with the *stream*
argument instead. (Contributed by Mariusz Felisiak in :gh:`115032`.)

* :mod:`mimetypes`:

Expand Down
86 changes: 43 additions & 43 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,14 @@ math
(Contributed by Bénédikt Tran in :gh:`135853`.)


mimetypes
---------

* Add ``application/toml``. (Contributed by Gil Forcada in :gh:`139959`.)
* Rename ``application/x-texinfo`` to ``application/texinfo``.
(Contributed by Charlie Lin in :gh:`140165`)


mmap
----

Expand Down Expand Up @@ -612,6 +620,17 @@ types
as described in :pep:`667`.


unicodedata
-----------

* The Unicode database has been updated to Unicode 17.0.0.

* Add :func:`unicodedata.isxidstart` and :func:`unicodedata.isxidcontinue`
functions to check whether a character can start or continue a
`Unicode Standard Annex #31 <https://www.unicode.org/reports/tr31/>`_ identifier.
(Contributed by Stan Ulbrych in :gh:`129117`.)


unittest
--------

Expand Down Expand Up @@ -720,14 +739,6 @@ importlib.resources
(Contributed by Semyon Moroz in :gh:`138044`)


mimetypes
---------

* Add ``application/toml``. (Contributed by Gil Forcada in :gh:`139959`.)
* Rename ``application/x-texinfo`` to ``application/texinfo``.
(Contributed by Charlie Lin in :gh:`140165`)


pathlib
-------

Expand Down Expand Up @@ -777,7 +788,7 @@ typing
(Contributed by Bénédikt Tran in :gh:`133817`.)

* Using ``TD = TypedDict("TD")`` or ``TD = TypedDict("TD", None)`` to
construct a :class:`~typing.TypedDict` type with zero field is no
construct a :class:`~typing.TypedDict` type with zero fields is no
longer supported. Use ``class TD(TypedDict): pass``
or ``TD = TypedDict("TD", {})`` instead.
(Contributed by Bénédikt Tran in :gh:`133823`.)
Expand Down Expand Up @@ -810,17 +821,6 @@ typing
(Contributed by Nikita Sobolev in :gh:`133601`.)


unicodedata
-----------

* The Unicode database has been updated to Unicode 17.0.0.

* Add :func:`unicodedata.isxidstart` and :func:`unicodedata.isxidcontinue`
functions to check whether a character can start or continue a
`Unicode Standard Annex #31 <https://www.unicode.org/reports/tr31/>`_ identifier.
(Contributed by Stan Ulbrych in :gh:`129117`.)


wave
----

Expand All @@ -847,7 +847,7 @@ New deprecations
* CLI:

* Deprecate :option:`-b` and :option:`!-bb` command-line options
and schedule them to become no-op in Python 3.17.
and schedule them to become no-ops in Python 3.17.
These were primarily helpers for the Python 2 -> 3 transition.
Starting with Python 3.17, no :exc:`BytesWarning` will be raised
for these cases; use a type checker instead.
Expand All @@ -858,8 +858,8 @@ New deprecations

* In hash function constructors such as :func:`~hashlib.new` or the
direct hash-named constructors such as :func:`~hashlib.md5` and
:func:`~hashlib.sha256`, their optional initial data parameter could
also be passed a keyword argument named ``data=`` or ``string=`` in
:func:`~hashlib.sha256`, the optional initial data parameter could
also be passed as a keyword argument named ``data=`` or ``string=`` in
various :mod:`hashlib` implementations.

Support for the ``string`` keyword argument name is now deprecated and
Expand Down Expand Up @@ -962,31 +962,11 @@ Changed C APIs
Porting to Python 3.15
----------------------

* :class:`sqlite3.Connection` APIs has been cleaned up.

* All parameters of :func:`sqlite3.connect` except *database* are now keyword-only.
* The first three parameters of methods :meth:`~sqlite3.Connection.create_function`
and :meth:`~sqlite3.Connection.create_aggregate` are now positional-only.
* The first parameter of methods :meth:`~sqlite3.Connection.set_authorizer`,
:meth:`~sqlite3.Connection.set_progress_handler` and
:meth:`~sqlite3.Connection.set_trace_callback` is now positional-only.

(Contributed by Serhiy Storchaka in :gh:`133595`.)

* Private functions promoted to public C APIs:

The |pythoncapi_compat_project| can be used to get most of these new
functions on Python 3.14 and older.

* :data:`resource.RLIM_INFINITY` is now always positive.
Passing a negative integer value that corresponded to its old value
(such as ``-1`` or ``-3``, depending on platform) to
:func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated.
(Contributed by Serhiy Storchaka in :gh:`137044`.)

* :meth:`~mmap.mmap.resize` has been removed on platforms that don't support the
underlying syscall, instead of raising a :exc:`SystemError`.


Removed C APIs
--------------
Expand Down Expand Up @@ -1106,3 +1086,23 @@ Porting to Python 3.15

This section lists previously described changes and other bugfixes
that may require changes to your code.

* :class:`sqlite3.Connection` APIs has been cleaned up.

* All parameters of :func:`sqlite3.connect` except *database* are now keyword-only.
* The first three parameters of methods :meth:`~sqlite3.Connection.create_function`
and :meth:`~sqlite3.Connection.create_aggregate` are now positional-only.
* The first parameter of methods :meth:`~sqlite3.Connection.set_authorizer`,
:meth:`~sqlite3.Connection.set_progress_handler` and
:meth:`~sqlite3.Connection.set_trace_callback` is now positional-only.

(Contributed by Serhiy Storchaka in :gh:`133595`.)

* :data:`resource.RLIM_INFINITY` is now always positive.
Passing a negative integer value that corresponded to its old value
(such as ``-1`` or ``-3``, depending on platform) to
:func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated.
(Contributed by Serhiy Storchaka in :gh:`137044`.)

* :meth:`~mmap.mmap.resize` has been removed on platforms that don't support the
underlying syscall, instead of raising a :exc:`SystemError`.
40 changes: 27 additions & 13 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ def __init__(
# These are always set to None here but may be non-None if a ForwardRef
# is created through __class__ assignment on a _Stringifier object.
self.__globals__ = None
# This may be either a cell object (for a ForwardRef referring to a single name)
# or a dict mapping cell names to cell objects (for a ForwardRef containing references
# to multiple names).
self.__cell__ = None
self.__extra_names__ = None
# These are initially None but serve as a cache and may be set to a non-None
Expand Down Expand Up @@ -117,7 +120,7 @@ def evaluate(
is_forwardref_format = True
case _:
raise NotImplementedError(format)
if self.__cell__ is not None:
if isinstance(self.__cell__, types.CellType):
try:
return self.__cell__.cell_contents
except ValueError:
Expand Down Expand Up @@ -160,11 +163,18 @@ def evaluate(

# 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:
# them to the globals. Similar reasoning applies to nonlocals stored in cells.
if type_params is not None or isinstance(self.__cell__, dict):
globals = dict(globals)
if type_params is not None:
for param in type_params:
globals[param.__name__] = param
if isinstance(self.__cell__, dict):
for cell_name, cell_value in self.__cell__.items():
try:
globals[cell_name] = cell_value.cell_contents
except ValueError:
pass
if self.__extra_names__:
locals = {**locals, **self.__extra_names__}

Expand Down Expand Up @@ -202,7 +212,7 @@ def evaluate(
except Exception:
return self
else:
new_locals.transmogrify()
new_locals.transmogrify(self.__cell__)
return result

def _evaluate(self, globalns, localns, type_params=_sentinel, *, recursive_guard):
Expand Down Expand Up @@ -274,7 +284,7 @@ def __hash__(self):
self.__forward_module__,
id(self.__globals__), # dictionaries are not hashable, so hash by identity
self.__forward_is_class__,
self.__cell__,
tuple(sorted(self.__cell__.items())) if isinstance(self.__cell__, dict) else self.__cell__,
self.__owner__,
tuple(sorted(self.__extra_names__.items())) if self.__extra_names__ else None,
))
Expand Down Expand Up @@ -642,13 +652,15 @@ def __missing__(self, key):
self.stringifiers.append(fwdref)
return fwdref

def transmogrify(self):
def transmogrify(self, cell_dict):
for obj in self.stringifiers:
obj.__class__ = ForwardRef
obj.__stringifier_dict__ = None # not needed for ForwardRef
if isinstance(obj.__ast_node__, str):
obj.__arg__ = obj.__ast_node__
obj.__ast_node__ = None
if cell_dict is not None and obj.__cell__ is None:
obj.__cell__ = cell_dict

def create_unique_name(self):
name = f"__annotationlib_name_{self.next_id}__"
Expand Down Expand Up @@ -712,7 +724,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):

globals = _StringifierDict({}, format=format)
is_class = isinstance(owner, type)
closure = _build_closure(
closure, _ = _build_closure(
annotate, owner, is_class, globals, allow_evaluation=False
)
func = types.FunctionType(
Expand Down Expand Up @@ -756,7 +768,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
is_class=is_class,
format=format,
)
closure = _build_closure(
closure, cell_dict = _build_closure(
annotate, owner, is_class, globals, allow_evaluation=True
)
func = types.FunctionType(
Expand All @@ -774,7 +786,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
except Exception:
pass
else:
globals.transmogrify()
globals.transmogrify(cell_dict)
return result

# Try again, but do not provide any globals. This allows us to return
Expand All @@ -786,7 +798,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
is_class=is_class,
format=format,
)
closure = _build_closure(
closure, cell_dict = _build_closure(
annotate, owner, is_class, globals, allow_evaluation=False
)
func = types.FunctionType(
Expand All @@ -797,7 +809,7 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):
kwdefaults=annotate.__kwdefaults__,
)
result = func(Format.VALUE_WITH_FAKE_GLOBALS)
globals.transmogrify()
globals.transmogrify(cell_dict)
if _is_evaluate:
if isinstance(result, ForwardRef):
return result.evaluate(format=Format.FORWARDREF)
Expand All @@ -822,14 +834,16 @@ def call_annotate_function(annotate, format, *, owner=None, _is_evaluate=False):

def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluation):
if not annotate.__closure__:
return None
return None, None
freevars = annotate.__code__.co_freevars
new_closure = []
cell_dict = {}
for i, cell in enumerate(annotate.__closure__):
if i < len(freevars):
name = freevars[i]
else:
name = "__cell__"
cell_dict[name] = cell
new_cell = None
if allow_evaluation:
try:
Expand All @@ -850,7 +864,7 @@ def _build_closure(annotate, owner, is_class, stringifier_dict, *, allow_evaluat
stringifier_dict.stringifiers.append(fwdref)
new_cell = types.CellType(fwdref)
new_closure.append(new_cell)
return tuple(new_closure)
return tuple(new_closure), cell_dict


def _stringify_single(anno):
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,21 @@ class RaisesAttributeError:
},
)

def test_nonlocal_in_annotation_scope(self):
class Demo:
nonlocal sequence_b
x: sequence_b
y: sequence_b[int]

fwdrefs = get_annotations(Demo, format=Format.FORWARDREF)

self.assertIsInstance(fwdrefs["x"], ForwardRef)
self.assertIsInstance(fwdrefs["y"], ForwardRef)

sequence_b = list
self.assertIs(fwdrefs["x"].evaluate(), list)
self.assertEqual(fwdrefs["y"].evaluate(), list[int])

def test_raises_error_from_value(self):
# test that if VALUE is the only supported format, but raises an error
# that error is propagated from get_annotations
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2283,6 +2283,15 @@ class Ints(enum.IntEnum):
self.assertEqual(Union[Literal[1], Literal[Ints.B], Literal[True]].__args__,
(Literal[1], Literal[Ints.B], Literal[True]))

def test_allow_non_types_in_or(self):
# gh-140348: Test that using | with a Union object allows things that are
# not allowed by is_unionable().
U1 = Union[int, str]
self.assertEqual(U1 | float, Union[int, str, float])
self.assertEqual(U1 | "float", Union[int, str, "float"])
self.assertEqual(float | U1, Union[float, int, str])
self.assertEqual("float" | U1, Union["float", int, str])


class TupleTests(BaseTestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
In :mod:`annotationlib`, improve evaluation of forward references to
nonlocal variables that are not yet defined when the annotations are
initially evaluated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix regression in Python 3.14.0 where using the ``|`` operator on a
:class:`typing.Union` object combined with an object that is not a type
would raise an error.
17 changes: 16 additions & 1 deletion Objects/unionobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,23 @@ static PyGetSetDef union_properties[] = {
{0}
};

static PyObject *
union_nb_or(PyObject *a, PyObject *b)
{
unionbuilder ub;
if (!unionbuilder_init(&ub, true)) {
return NULL;
}
if (!unionbuilder_add_single(&ub, a) ||
!unionbuilder_add_single(&ub, b)) {
unionbuilder_finalize(&ub);
return NULL;
}
return make_union(&ub);
}

static PyNumberMethods union_as_number = {
.nb_or = _Py_union_type_or, // Add __or__ function
.nb_or = union_nb_or, // Add __or__ function
};

static const char* const cls_attrs[] = {
Expand Down
Loading