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
2 changes: 1 addition & 1 deletion Doc/howto/logging-cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3920,7 +3920,7 @@ subclassed handler which looks something like this::

You'll need to be familiar with RFC 5424 to fully understand the above code, and it
may be that you have slightly different needs (e.g. for how you pass structural data
to the log). Nevertheless, the above should be adaptable to your speciric needs. With
to the log). Nevertheless, the above should be adaptable to your specific needs. With
the above handler, you'd pass structured data using something like this::

sd = {
Expand Down
2 changes: 1 addition & 1 deletion Doc/howto/mro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ prescription:
the tail of any of the other lists, then add it to the linearization
of C and remove it from the lists in the merge, otherwise look at the
head of the next list and take it, if it is a good head. Then repeat
the operation until all the class are removed or it is impossible to
the operation until all the classes are removed or it is impossible to
find good heads. In this case, it is impossible to construct the
merge, Python 2.3 will refuse to create the class C and will raise an
exception.*
Expand Down
28 changes: 20 additions & 8 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,10 @@ Input options

The curses library does "line-breakout optimization" by looking for typeahead
periodically while updating the screen. If input is found, and it is coming
from a tty, the current update is postponed until refresh or doupdate is called
again, allowing faster response to commands typed in advance. This function
allows specifying a different file descriptor for typeahead checking.
from a tty, the current update is postponed until :meth:`~window.refresh` or
:func:`doupdate` is called again, allowing faster response to commands typed
in advance. This function allows specifying a different file descriptor for
typeahead checking.

.. function:: is_cbreak()

Expand Down Expand Up @@ -702,7 +703,8 @@ labels.
(eight labels). Where the underlying curses library supports them, ``2``
gives 4-4-4 (twelve labels) and ``3`` gives 4-4-4 with an index line.

Must be called before :func:`initscr` or :func:`newterm`.
Must be called before :func:`initscr` or :func:`newterm`,
and affects only the screen created next.

.. versionadded:: next

Expand Down Expand Up @@ -1067,8 +1069,10 @@ Utilities

.. function:: filter()

The :func:`.filter` routine, if used, must be called before :func:`initscr` is
called. The effect is that, during the initialization, :envvar:`LINES` is set to ``1``; the
The :func:`.filter` routine, if used, must be called before :func:`initscr`
or :func:`newterm` is called,
and affects every screen created afterwards.
The effect is that, during the initialization, :envvar:`LINES` is set to ``1``; the
capabilities ``clear``, ``cup``, ``cud``, ``cud1``, ``cuu1``, ``cuu``, ``vpa`` are disabled; and the ``home``
string is set to the value of ``cr``. The effect is that the cursor is confined to
the current line, and so are screen updates. This may be used for enabling
Expand All @@ -1087,8 +1091,10 @@ Utilities

.. function:: use_env(flag)

If used, this function should be called before :func:`initscr` or newterm are
called. When *flag* is ``False``, the values of lines and columns specified in the
If used, this function should be called before :func:`initscr` or
:func:`newterm` are called,
and affects every screen created afterwards.
When *flag* is ``False``, the values of lines and columns specified in the
terminfo database will be used, even if environment variables :envvar:`LINES`
and :envvar:`COLUMNS` (used by default) are set, or if curses is running in a
window (in which case default behavior would be to use the window size if
Expand All @@ -1106,6 +1112,9 @@ Utilities
to distinguish between an individual escape character entered on the
keyboard from escape sequences sent by cursor and function keys.

Depending on the curses library, the setting may apply to all screens,
not only to the current one.

.. versionadded:: 3.9

.. function:: get_tabsize()
Expand All @@ -1119,6 +1128,9 @@ Utilities
Sets the number of columns used by the curses library when converting a tab
character to spaces as it adds the tab to a window.

Depending on the curses library, the setting may apply to all screens,
not only to the current one, and creating a screen may reset it.

.. versionadded:: 3.9

.. function:: napms(ms)
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/logging.config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ following configuration::
'bar' : 'baz',
'spam' : 99.9,
'answer' : 42,
'.' {
'.' : {
'foo': 'bar',
'baz': 'bozz'
}
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/pdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ can be overridden by the local file.
def inner(x):
1 / x

out()
out()

calling ``pdb.pm()`` will allow to move between exceptions::

Expand Down
8 changes: 4 additions & 4 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -798,12 +798,12 @@ Generator functions
single: generator; function
single: generator; iterator

A function or method which uses the :keyword:`yield` statement (see section
:ref:`yield`) is called a :dfn:`generator function`. Such a function, when
A function or method which contains a :keyword:`yield` expression (see section
:ref:`yieldexpr`) is called a :dfn:`generator function`. Such a function, when
called, always returns an :term:`iterator` object which can be used to
execute the body of the function: calling the iterator's
:meth:`iterator.__next__` method will cause the function to execute until
it provides a value using the :keyword:`!yield` statement. When the
it provides a value using the :keyword:`!yield` expression. When the
function executes a :keyword:`return` statement or falls off the end, a
:exc:`StopIteration` exception is raised and the iterator will have
reached the end of the set of values to be returned.
Expand All @@ -830,7 +830,7 @@ Asynchronous generator functions
single: asynchronous generator; asynchronous iterator

A function or method which is defined using :keyword:`async def` and
which uses the :keyword:`yield` statement is called a
which contains a :keyword:`yield` expression is called a
:dfn:`asynchronous generator function`. Such a function, when called,
returns an :term:`asynchronous iterator` object which can be used in an
:keyword:`async for` statement to execute the body of the function.
Expand Down
21 changes: 18 additions & 3 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,27 @@ def evaluate(

arg = self.__forward_arg__
if arg.isidentifier() and not keyword.iskeyword(arg):
resolved = _sentinel
if arg in locals:
return locals[arg]
resolved = locals[arg]
elif arg in globals:
return globals[arg]
resolved = globals[arg]
elif hasattr(builtins, arg):
return getattr(builtins, arg)
resolved = getattr(builtins, arg)

if resolved is not _sentinel:
if isinstance(resolved, types.LazyImportType):
# We try reifying the lazy object. If this fails, we propagate
# the error in the VALUE format and leave the
# ForwardRef unresolved in the FORWARDREF format.
try:
return resolved.resolve()
except Exception:
if not is_forwardref_format:
raise
return self
else:
return resolved
elif is_forwardref_format:
return self
else:
Expand Down
112 changes: 112 additions & 0 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,118 @@ def test_evaluate_forwardref_format(self):
support.EqualToForwardRef('"a" + 1'),
)

def test_evaluate_lazy_import(self):
ns = {}
exec(
textwrap.dedent(
"""
lazy from test.test_lazy_import.data.basic2 import x
lazy from test.test_lazy_import.data.broken_module import y

class A:
a: x

class B:
b: y
"""
),
ns,
)
self.addCleanup(
import_helper.unload, "test.test_lazy_import.data.basic2"
)
self.addCleanup(
import_helper.unload, "test.test_lazy_import.data.broken_module"
)
self.assertIs(type(ns["x"]), types.LazyImportType)
self.assertIs(type(ns["y"]), types.LazyImportType)

# The lazy import resolves successfully:
for format in (Format.VALUE, Format.FORWARDREF):
with self.subTest(format=format):
self.assertEqual(
ForwardRef("x").evaluate(globals=ns, format=format), 42
)
self.assertEqual(
ForwardRef("x").evaluate(locals=ns, format=format), 42
)
self.assertEqual(
get_annotations(ns["A"], format=Format.FORWARDREF), {"a": 42}
)

# The lazy import fails to resolve:
fr = ForwardRef("y")
with self.assertRaisesRegex(ValueError, "always fails to import"):
fr.evaluate(globals=ns, format=Format.VALUE)
with self.assertRaisesRegex(ValueError, "always fails to import"):
fr.evaluate(locals=ns, format=Format.VALUE)
self.assertIs(fr.evaluate(globals=ns, format=Format.FORWARDREF), fr)
self.assertIs(fr.evaluate(locals=ns, format=Format.FORWARDREF), fr)

annos = get_annotations(ns["B"], format=Format.FORWARDREF)
self.assertEqual(
annos,
{"b": support.EqualToForwardRef("y", is_class=True, owner=ns["B"])},
)
with self.assertRaisesRegex(ValueError, "always fails to import"):
annos["b"].evaluate(format=Format.VALUE)
with self.assertRaisesRegex(ValueError, "always fails to import"):
get_annotations(ns["B"], format=Format.VALUE)

def test_get_annotations_lazy_import(self):
ns = {}
exec(
textwrap.dedent(
"""
lazy from test.test_lazy_import.data.basic2 import x
lazy from test.test_lazy_import.data.broken_module import y

class A:
a: object.fail
b: x

class B:
a: object.fail
b: y
"""
),
ns,
)
self.addCleanup(
import_helper.unload, "test.test_lazy_import.data.basic2"
)
self.addCleanup(
import_helper.unload, "test.test_lazy_import.data.broken_module"
)
self.assertIs(type(ns["x"]), types.LazyImportType)
self.assertIs(type(ns["y"]), types.LazyImportType)

annos = get_annotations(ns["A"], format=Format.FORWARDREF)
self.assertEqual(
annos,
{
"a": support.EqualToForwardRef(
"object.fail", is_class=True, owner=ns["A"]
),
"b": 42,
},
)

annos = get_annotations(ns["B"], format=Format.FORWARDREF)
self.assertEqual(
annos,
{
"a": support.EqualToForwardRef(
"object.fail", is_class=True, owner=ns["B"]
),
"b": support.EqualToForwardRef(
"y", is_class=True, owner=ns["B"]
),
},
)
with self.assertRaisesRegex(ValueError, "always fails to import"):
annos["b"].evaluate(format=Format.VALUE)

def test_evaluate_notimplemented_format(self):
class C:
x: alias
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
In ``makeunicodedata.py``, the script for updating Unicode data, skip
generating ``stringprep.py`` if the current interpreter's Unicode data
version does not match the target version.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
:meth:`annotationlib.ForwardRef.evaluate` now resolves :ref:`lazy import
<lazy-imports>` proxies found in the namespace. With
:attr:`~annotationlib.Format.FORWARDREF`, a lazy import that fails to resolve
now results in a :class:`~annotationlib.ForwardRef` instead of the
:class:`lazy import proxy <types.LazyImportType>`, and with :attr:`~annotationlib.Format.VALUE` the underlying exception is
propagated.
37 changes: 32 additions & 5 deletions Tools/unicode/makeunicodedata.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
SCRIPT = os.path.normpath(sys.argv[0])
VERSION = "3.3"

# Local cache location
DATA_DIR = os.path.join('Tools', 'unicode', 'data')

# The Unicode Database
# --------------------
# When changing UCD version please update
Expand Down Expand Up @@ -816,10 +819,31 @@ def makeunicodename(unicode, trace):
def makestringprep():
FILE = "Lib/stringprep.py"

RFC_LOCAL = os.path.join(DATA_DIR, "rfc3454.txt")
RFC_URL = "https://www.rfc-editor.org/rfc/rfc3454.txt"

print("--- Preparing", FILE, "...")

# mkstringprep expects a local copy of RFC 3454. Download it now.
# (stringprep is used for URL handling, and if it's not matched
# with the compiled unicodedata, downloads would fail.)
if not os.path.exists(RFC_LOCAL):
download_data(RFC_LOCAL, RFC_URL)

MKSTRINGPREP = "Tools/unicode/mkstringprep.py"

# mkstringprep needs to be run with a Python version that has "its"
# unicode data, since it uses str.lower() and similar.
import unicodedata
if unicodedata.unidata_version != UNIDATA_VERSION:
print()
print("!! Skipping mkstringprep -- mismatched Unicode version !!")
print()
print("Please compile CPython with the updated Unicode database,")
print("then use that interpreter to run:")
print(f" python {MKSTRINGPREP} > {FILE}")
return

with open(FILE, "w") as f:
f.truncate()
subprocess.check_call([sys.executable, MKSTRINGPREP], stdout=f)
Expand Down Expand Up @@ -929,26 +953,29 @@ class Difference(Exception):pass
normalization_changes))


DATA_DIR = os.path.join('Tools', 'unicode', 'data')

def open_data(template, version):
local = os.path.join(DATA_DIR, template % ('-'+version,))
if not os.path.exists(local):
import urllib.request
if version == '3.2.0':
# irregular url structure
url = ('https://www.unicode.org/Public/3.2-Update/'+template) % ('-'+version,)
else:
url = ('https://www.unicode.org/Public/%s/ucd/'+template) % (version, '')
os.makedirs(os.path.dirname(local), exist_ok=True)
urllib.request.urlretrieve(url, filename=local)
download_data(local, url)
if local.endswith('.txt'):
return open(local, encoding='utf-8')
else:
# Unihan.zip
return open(local, 'rb')


def download_data(local, url):
import urllib.request
os.makedirs(os.path.dirname(local), exist_ok=True)
print(f'Downloading {url} to {local}')
urllib.request.urlretrieve(url, filename=local)


def expand_range(char_range: str) -> Iterator[int]:
'''
Parses ranges of code points, as described in UAX #44:
Expand Down
14 changes: 1 addition & 13 deletions Tools/unicode/mkstringprep.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import re
import os
import unicodedata as unicodedata_current
from unicodedata import ucd_3_2_0 as unicodedata_320

FILENAME = "Tools/unicode/data/rfc3454.txt"
URL = "https://www.rfc-editor.org/rfc/rfc3454.txt"

def gen_category(cats):
for i in range(0, 0x110000):
if unicodedata_320.category(chr(i)) in cats:
Expand Down Expand Up @@ -52,15 +48,7 @@ def compact_set(l):

############## Read the tables in the RFC #######################

try:
data_file = open(FILENAME, encoding='utf-8')
except FileNotFoundError:
import urllib.request
os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
urllib.request.urlretrieve(URL, filename=FILENAME)
data_file = open(FILENAME, encoding='utf-8')

with data_file:
with open("Tools/unicode/data/rfc3454.txt", encoding='utf-8') as data_file:
data = data_file.readlines()

tables = []
Expand Down
Loading