Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into sigdocs
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Sep 27, 2024
2 parents 38c4b42 + 365dffb commit c33c4cc
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 21 deletions.
9 changes: 9 additions & 0 deletions Doc/c-api/unicode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ APIs:
This is the recommended way to allocate a new Unicode object. Objects
created using this function are not resizable.
On error, set an exception and return ``NULL``.
.. versionadded:: 3.3
Expand Down Expand Up @@ -614,6 +616,8 @@ APIs:
Return the length of the Unicode object, in code points.
On error, set an exception and return ``-1``.
.. versionadded:: 3.3
Expand Down Expand Up @@ -657,6 +661,8 @@ APIs:
not out of bounds, and that the object can be modified safely (i.e. that it
its reference count is one).
Return ``0`` on success, ``-1`` on error with an exception set.
.. versionadded:: 3.3
Expand All @@ -666,6 +672,8 @@ APIs:
Unicode object and the index is not out of bounds, in contrast to
:c:func:`PyUnicode_READ_CHAR`, which performs no error checking.
Return character on success, ``-1`` on error with an exception set.
.. versionadded:: 3.3
Expand All @@ -674,6 +682,7 @@ APIs:
Return a substring of *unicode*, from character index *start* (included) to
character index *end* (excluded). Negative indices are not supported.
On error, set an exception and return ``NULL``.
.. versionadded:: 3.3
Expand Down
6 changes: 4 additions & 2 deletions Doc/tutorial/controlflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,10 @@ after the loop finishes its final iteration, that is, if no break occurred.

In a :keyword:`while` loop, it's executed after the loop's condition becomes false.

In either kind of loop, the :keyword:`!else` clause is **not** executed
if the loop was terminated by a :keyword:`break`.
In either kind of loop, the :keyword:`!else` clause is **not** executed if the
loop was terminated by a :keyword:`break`. Of course, other ways of ending the
loop early, such as a :keyword:`return` or a raised exception, will also skip
execution of the :keyword:`else` clause.

This is exemplified in the following :keyword:`!for` loop,
which searches for prime numbers::
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test__interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,6 @@ def test_execution_namespace_is_main(self):
ns.pop('__loader__')
self.assertEqual(ns, {
'__name__': '__main__',
'__annotations__': {},
'__doc__': None,
'__package__': None,
'__spec__': None,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,7 @@ def setUp(self):

@force_not_colorized
def test_exposed_globals_in_repl(self):
pre = "['__annotations__', '__builtins__'"
pre = "['__builtins__'"
post = "'__loader__', '__name__', '__package__', '__spec__']"
output, exit_code = self.run_repl(["sorted(dir())", "exit()"])
if "can't use pyrepl" in output:
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_ttk/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,13 @@ def test_element_create_image(self):
foreground='blue', background='yellow')
img3 = tkinter.BitmapImage(master=self.root, file=imgfile,
foreground='white', background='black')
style.element_create('Button.button', 'image',
style.element_create('TestButton.button', 'image',
img1, ('pressed', img2), ('active', img3),
border=(2, 4), sticky='we')
self.assertIn('Button.button', style.element_names())
self.assertIn('TestButton.button', style.element_names())

style.layout('Button', [('Button.button', {'sticky': 'news'})])
b = ttk.Button(self.root, style='Button')
style.layout('TestButton', [('TestButton.button', {'sticky': 'news'})])
b = ttk.Button(self.root, style='TestButton')
b.pack(expand=True, fill='both')
self.assertEqual(b.winfo_reqwidth(), 16)
self.assertEqual(b.winfo_reqheight(), 16)
Expand Down
72 changes: 72 additions & 0 deletions Lib/test/test_unittest/testmock/testhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
Mock, ANY, _CallList, patch, PropertyMock, _callable
)

from dataclasses import dataclass, field, InitVar
from datetime import datetime
from functools import partial
from typing import ClassVar

class SomeClass(object):
def one(self, a, b): pass
Expand Down Expand Up @@ -1034,6 +1036,76 @@ def f(a): pass
self.assertEqual(mock.mock_calls, [])
self.assertEqual(rv.mock_calls, [])

def test_dataclass_post_init(self):
@dataclass
class WithPostInit:
a: int = field(init=False)
b: int = field(init=False)
def __post_init__(self):
self.a = 1
self.b = 2

for mock in [
create_autospec(WithPostInit, instance=True),
create_autospec(WithPostInit()),
]:
with self.subTest(mock=mock):
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)

# Classes do not have these fields:
mock = create_autospec(WithPostInit)
msg = "Mock object has no attribute"
with self.assertRaisesRegex(AttributeError, msg):
mock.a
with self.assertRaisesRegex(AttributeError, msg):
mock.b

def test_dataclass_default(self):
@dataclass
class WithDefault:
a: int
b: int = 0

for mock in [
create_autospec(WithDefault, instance=True),
create_autospec(WithDefault(1)),
]:
with self.subTest(mock=mock):
self.assertIsInstance(mock.a, int)
self.assertIsInstance(mock.b, int)

def test_dataclass_with_method(self):
@dataclass
class WithMethod:
a: int
def b(self) -> int:
return 1

for mock in [
create_autospec(WithMethod, instance=True),
create_autospec(WithMethod(1)),
]:
with self.subTest(mock=mock):
self.assertIsInstance(mock.a, int)
mock.b.assert_not_called()

def test_dataclass_with_non_fields(self):
@dataclass
class WithNonFields:
a: ClassVar[int]
b: InitVar[int]

msg = "Mock object has no attribute"
for mock in [
create_autospec(WithNonFields, instance=True),
create_autospec(WithNonFields(1)),
]:
with self.subTest(mock=mock):
with self.assertRaisesRegex(AttributeError, msg):
mock.a
with self.assertRaisesRegex(AttributeError, msg):
mock.b

class TestCallList(unittest.TestCase):

Expand Down
22 changes: 16 additions & 6 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import pkgutil
from inspect import iscoroutinefunction
import threading
from dataclasses import fields, is_dataclass
from types import CodeType, ModuleType, MethodType
from unittest.util import safe_repr
from functools import wraps, partial
Expand Down Expand Up @@ -2756,7 +2757,15 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
raise InvalidSpecError(f'Cannot autospec a Mock object. '
f'[object={spec!r}]')
is_async_func = _is_async_func(spec)
_kwargs = {'spec': spec}

entries = [(entry, _missing) for entry in dir(spec)]
if is_type and instance and is_dataclass(spec):
dataclass_fields = fields(spec)
entries.extend((f.name, f.type) for f in dataclass_fields)
_kwargs = {'spec': [f.name for f in dataclass_fields]}
else:
_kwargs = {'spec': spec}

if spec_set:
_kwargs = {'spec_set': spec}
elif spec is None:
Expand Down Expand Up @@ -2813,7 +2822,7 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
_name='()', _parent=mock,
wraps=wrapped)

for entry in dir(spec):
for entry, original in entries:
if _is_magic(entry):
# MagicMock already does the useful magic methods for us
continue
Expand All @@ -2827,10 +2836,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
# AttributeError on being fetched?
# we could be resilient against it, or catch and propagate the
# exception when the attribute is fetched from the mock
try:
original = getattr(spec, entry)
except AttributeError:
continue
if original is _missing:
try:
original = getattr(spec, entry)
except AttributeError:
continue

child_kwargs = {'spec': original}
# Wrap child attributes also.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The ``__main__`` module no longer always contains an ``__annotations__``
dictionary in its global namespace.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add support for :func:`dataclasses.dataclass` in
:func:`unittest.mock.create_autospec`. Now ``create_autospec`` will check
for potential dataclasses and use :func:`dataclasses.fields` function to
retrieve the spec information.
8 changes: 1 addition & 7 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2503,18 +2503,12 @@ finalize_subinterpreters(void)
static PyStatus
add_main_module(PyInterpreterState *interp)
{
PyObject *m, *d, *ann_dict;
PyObject *m, *d;
m = PyImport_AddModuleObject(&_Py_ID(__main__));
if (m == NULL)
return _PyStatus_ERR("can't create __main__ module");

d = PyModule_GetDict(m);
ann_dict = PyDict_New();
if ((ann_dict == NULL) ||
(PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {
return _PyStatus_ERR("Failed to initialize __main__.__annotations__");
}
Py_DECREF(ann_dict);

int has_builtins = PyDict_ContainsString(d, "__builtins__");
if (has_builtins < 0) {
Expand Down

0 comments on commit c33c4cc

Please sign in to comment.