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/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ the following methods and attributes:

.. method:: window.attron(attr)

Add attribute *attr* from the "background" set applied to all writes to the
Add attribute *attr* to the "background" set applied to all writes to the
current window.


Expand Down
3 changes: 3 additions & 0 deletions Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ An :class:`IMAP4` instance has the following methods:
the password. Will only work if the server ``CAPABILITY`` response includes the
phrase ``AUTH=CRAM-MD5``.

.. versionchanged:: next
An :exc:`IMAP4.error` is raised if MD5 support is not available.


.. method:: IMAP4.logout()

Expand Down
28 changes: 14 additions & 14 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Summary -- release highlights
.. This section singles out the most important changes in Python 3.14.
Brevity is key.

Python 3.14 beta is the pre-release of the next version of the Python
Python 3.14 will be the latest stable release of the Python
programming language, with a mix of changes to the language, the
implementation and the standard library.

Expand Down Expand Up @@ -635,7 +635,7 @@ Improved error messages
misspellings may still result in regular syntax errors.
(Contributed by Pablo Galindo in :gh:`132449`.)

* When unpacking assignment fails due to incorrect number of variables, the
* When an unpacking assignment fails due to an incorrect number of variables, the
error message prints the received number of values in more cases than before.
(Contributed by Tushar Sadhwani in :gh:`122239`.)

Expand Down Expand Up @@ -763,7 +763,7 @@ ABI-compatible changes in the future.

Complete the :pep:`587` :ref:`PyConfig C API <pyconfig_api>` by adding
:c:func:`PyInitConfig_AddModule` which can be used to add a built-in extension
module; feature previously referred to as the “inittab”.
module; a feature previously referred to as the “inittab”.

Add :c:func:`PyConfig_Get` and :c:func:`PyConfig_Set` functions to get and set
the current runtime configuration.
Expand Down Expand Up @@ -1051,7 +1051,7 @@ Concurrent safe warnings control
The :class:`warnings.catch_warnings` context manager will now optionally
use a context variable for warning filters. This is enabled by setting
the :data:`~sys.flags.context_aware_warnings` flag, either with the ``-X``
command-line option or an environment variable. This gives predicable
command-line option or an environment variable. This gives predictable
warnings control when using :class:`~warnings.catch_warnings` combined with
multiple threads or asynchronous tasks. The flag defaults to true for the
free-threaded build and false for the GIL-enabled build.
Expand Down Expand Up @@ -1152,7 +1152,7 @@ Other language changes
unlike ``\Z``, which has subtly different behavior.
(Contributed by Serhiy Storchaka in :gh:`133306`.)

* ``\B`` in :mod:`regular expression <re>` now matches empty input string.
* ``\B`` in :mod:`regular expression <re>` now matches the empty input string.
Now it is always the opposite of ``\b``.
(Contributed by Serhiy Storchaka in :gh:`124130`.)

Expand Down Expand Up @@ -1221,7 +1221,7 @@ PEP 765: Disallow ``return``/``break``/``continue`` that exit a ``finally`` bloc
---------------------------------------------------------------------------------

The compiler emits a :exc:`SyntaxWarning` when a :keyword:`return`, :keyword:`break` or
:keyword:`continue` statements appears where it exits a :keyword:`finally` block.
:keyword:`continue` statement appears where it exits a :keyword:`finally` block.
This change is specified in :pep:`765`.


Expand Down Expand Up @@ -1278,7 +1278,7 @@ ast
(Contributed by Irit Katriel in :gh:`130139`.)

* Add new ``--feature-version``, ``--optimize``, ``--show-empty`` options to
command-line interface.
the command-line interface.
(Contributed by Semyon Moroz in :gh:`133367`.)


Expand Down Expand Up @@ -2157,7 +2157,7 @@ unittest
:meth:`~unittest.TestCase.assertNotStartsWith`,
:meth:`~unittest.TestCase.assertEndsWith` and
:meth:`~unittest.TestCase.assertNotEndsWith` check whether the Unicode
or byte string starts or ends with particular string(s).
or byte string starts or ends with particular strings.

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

Expand Down Expand Up @@ -2223,15 +2223,15 @@ webbrowser
supported browsers on macOS.


zipinfo
zipfile
-------

* Added :func:`ZipInfo._for_archive <zipfile.ZipInfo._for_archive>`
to resolve suitable defaults for a :class:`~zipfile.ZipInfo` object
as used by :func:`ZipFile.writestr <zipfile.ZipFile.writestr>`.
(Contributed by Bénédikt Tran in :gh:`123424`.)

* :meth:`zipfile.ZipFile.writestr` now respect ``SOURCE_DATE_EPOCH`` that
* :meth:`zipfile.ZipFile.writestr` now respects ``SOURCE_DATE_EPOCH`` that
distributions can set centrally and have build tools consume this in order
to produce reproducible output.
(Contributed by Jiahao Li in :gh:`91279`.)
Expand Down Expand Up @@ -2379,7 +2379,7 @@ zlib
* On Windows, `zlib-ng <https://github.com/zlib-ng/zlib-ng>`__
is now used as the implementation of the :mod:`zlib` module
in the default binaries.
There are no known incompatabilities between ``zlib-ng``
There are no known incompatibilities between ``zlib-ng``
and the previously-used ``zlib`` implementation.
This should result in better performance at all compression levels.

Expand Down Expand Up @@ -2839,7 +2839,7 @@ Changes in the Python API
rather than collecting generation 1.
* Other calls to :func:`!gc.collect` are unchanged.

* The :func:`locale.nl_langinfo` function now sets temporarily the ``LC_CTYPE``
* The :func:`locale.nl_langinfo` function now temporarily sets the ``LC_CTYPE``
locale in some cases.
This temporary change affects other threads.
(Contributed by Serhiy Storchaka in :gh:`69998`.)
Expand Down Expand Up @@ -3019,8 +3019,8 @@ New features
and get an attribute of the module.
(Contributed by Victor Stinner in :gh:`128911`.)

* Add support for a new ``p`` format unit in :c:func:`Py_BuildValue` that allows to
take a C integer and produce a Python :class:`bool` object. (Contributed by
* Add support for a new ``p`` format unit in :c:func:`Py_BuildValue` that allows
taking a C integer and produces a Python :class:`bool` object. (Contributed by
Pablo Galindo in :issue:`45325`.)

* Add :c:func:`PyUnstable_Object_IsUniqueReferencedTemporary` to determine if an object
Expand Down
19 changes: 19 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,25 @@ Other language changes
* Several error messages incorrectly using the term "argument" have been corrected.
(Contributed by Stan Ulbrych in :gh:`133382`.)

* The interpreter now tries to provide a suggestion when
:func:`delattr` fails due to a missing attribute.
When an attribute name that closely resembles an existing attribute is used,
the interpreter will suggest the correct attribute name in the error message.
For example:

.. doctest::

>>> class A:
... pass
>>> a = A()
>>> a.abcde = 1
>>> del a.abcdf # doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'abcdf'. Did you mean: 'abcde'?

(Contributed by Nikita Sobolev and Pranjal Prajapati in :gh:`136588`.)

* Unraisable exceptions are now highlighted with color by default. This can be
controlled by :ref:`environment variables <using-on-controlling-color>`.
(Contributed by Peter Bierma in :gh:`134170`.)
Expand Down
14 changes: 9 additions & 5 deletions Lib/dbm/sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,22 @@ def __init__(self, path, /, *, flag, mode):
# We use the URI format when opening the database.
uri = _normalize_uri(path)
uri = f"{uri}?mode={flag}"
if flag == "ro":
# Add immutable=1 to allow read-only SQLite access even if wal/shm missing
uri += "&immutable=1"

try:
self._cx = sqlite3.connect(uri, autocommit=True, uri=True)
except sqlite3.Error as exc:
raise error(str(exc))

# This is an optimization only; it's ok if it fails.
with suppress(sqlite3.OperationalError):
self._cx.execute("PRAGMA journal_mode = wal")
if flag != "ro":
# This is an optimization only; it's ok if it fails.
with suppress(sqlite3.OperationalError):
self._cx.execute("PRAGMA journal_mode = wal")

if flag == "rwc":
self._execute(BUILD_TABLE)
if flag == "rwc":
self._execute(BUILD_TABLE)

def _execute(self, *args, **kwargs):
if not self._cx:
Expand Down
16 changes: 12 additions & 4 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# GET/SETANNOTATION contributed by Tomas Lindroos <skitta@abo.fi> June 2005.
# IDLE contributed by Forest <forestix@nom.one> August 2024.

__version__ = "2.59"
__version__ = "2.60"

import binascii, errno, random, re, socket, subprocess, sys, time, calendar
from datetime import datetime, timezone, timedelta
Expand Down Expand Up @@ -725,9 +725,17 @@ def login_cram_md5(self, user, password):
def _CRAM_MD5_AUTH(self, challenge):
""" Authobject to use with CRAM-MD5 authentication. """
import hmac
pwd = (self.password.encode('utf-8') if isinstance(self.password, str)
else self.password)
return self.user + " " + hmac.HMAC(pwd, challenge, 'md5').hexdigest()

if isinstance(self.password, str):
password = self.password.encode('utf-8')
else:
password = self.password

try:
authcode = hmac.HMAC(password, challenge, 'md5')
except ValueError: # HMAC-MD5 is not available
raise self.error("CRAM-MD5 authentication is not supported")
return f"{self.user} {authcode.hexdigest()}"


def logout(self):
Expand Down
3 changes: 2 additions & 1 deletion Lib/json/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def main():
'to validate and pretty-print JSON objects.')
parser = argparse.ArgumentParser(description=description, color=True)
parser.add_argument('infile', nargs='?',
help='a JSON file to be validated or pretty-printed',
help='a JSON file to be validated or pretty-printed; '
'defaults to stdin',
default='-')
parser.add_argument('outfile', nargs='?',
help='write the output of infile to outfile',
Expand Down
45 changes: 45 additions & 0 deletions Lib/test/test_dbm_sqlite3.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
import stat
import sys
import unittest
from contextlib import closing
Expand Down Expand Up @@ -90,6 +92,49 @@ def test_readonly_iter(self):
self.assertEqual([k for k in self.db], [b"key1", b"key2"])


class ReadOnlyFilesystem(unittest.TestCase):

def setUp(self):
self.test_dir = os_helper.TESTFN
self.addCleanup(os_helper.rmtree, self.test_dir)
os.mkdir(self.test_dir)
self.db_path = os.path.join(self.test_dir, "test.db")

db = dbm_sqlite3.open(self.db_path, "c")
db[b"key"] = b"value"
db.close()

def test_readonly_file_read(self):
os.chmod(self.db_path, stat.S_IREAD)
with dbm_sqlite3.open(self.db_path, "r") as db:
self.assertEqual(db[b"key"], b"value")

def test_readonly_file_write(self):
os.chmod(self.db_path, stat.S_IREAD)
with dbm_sqlite3.open(self.db_path, "w") as db:
with self.assertRaises(dbm_sqlite3.error):
db[b"newkey"] = b"newvalue"

def test_readonly_dir_read(self):
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
with dbm_sqlite3.open(self.db_path, "r") as db:
self.assertEqual(db[b"key"], b"value")

def test_readonly_dir_write(self):
os.chmod(self.test_dir, stat.S_IREAD | stat.S_IEXEC)
with dbm_sqlite3.open(self.db_path, "w") as db:
try:
db[b"newkey"] = b"newvalue"
modified = True # on Windows and macOS
except dbm_sqlite3.error:
modified = False
with dbm_sqlite3.open(self.db_path, "r") as db:
if modified:
self.assertEqual(db[b"newkey"], b"newvalue")
else:
self.assertNotIn(b"newkey", db)


class ReadWrite(_SQLiteDbmTests):

def setUp(self):
Expand Down
56 changes: 27 additions & 29 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
import socket

from test.support import verbose, run_with_tz, run_with_locale, cpython_only
from test.support import hashlib_helper
from test.support import threading_helper
from test.support import hashlib_helper, threading_helper
import unittest
from unittest import mock
from datetime import datetime, timezone, timedelta
Expand Down Expand Up @@ -256,7 +255,20 @@ def cmd_IDLE(self, tag, args):
self._send_tagged(tag, 'BAD', 'Expected DONE')


class NewIMAPTestsMixin():
class AuthHandler_CRAM_MD5(SimpleIMAPHandler):
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
def cmd_AUTHENTICATE(self, tag, args):
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
'VzdG9uLm1jaS5uZXQ=')
r = yield
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
else:
self._send_tagged(tag, 'NO', 'No access')


class NewIMAPTestsMixin:
client = None

def _setup(self, imap_handler, connect=True):
Expand Down Expand Up @@ -439,40 +451,26 @@ def cmd_AUTHENTICATE(self, tag, args):

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_bytes(self):
class AuthHandler(SimpleIMAPHandler):
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
def cmd_AUTHENTICATE(self, tag, args):
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
'VzdG9uLm1jaS5uZXQ=')
r = yield
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
else:
self._send_tagged(tag, 'NO', 'No access')
client, _ = self._setup(AuthHandler)
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
client, _ = self._setup(AuthHandler_CRAM_MD5)
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
self.assertEqual(ret, "OK")

@hashlib_helper.requires_hashdigest('md5', openssl=True)
def test_login_cram_md5_plain_text(self):
class AuthHandler(SimpleIMAPHandler):
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
def cmd_AUTHENTICATE(self, tag, args):
self._send_textline('+ PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2Uucm'
'VzdG9uLm1jaS5uZXQ=')
r = yield
if (r == b'dGltIGYxY2E2YmU0NjRiOWVmYT'
b'FjY2E2ZmZkNmNmMmQ5ZjMy\r\n'):
self._send_tagged(tag, 'OK', 'CRAM-MD5 successful')
else:
self._send_tagged(tag, 'NO', 'No access')
client, _ = self._setup(AuthHandler)
self.assertTrue('AUTH=CRAM-MD5' in client.capabilities)
client, _ = self._setup(AuthHandler_CRAM_MD5)
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
ret, _ = client.login_cram_md5("tim", "tanstaaftanstaaf")
self.assertEqual(ret, "OK")

@hashlib_helper.block_algorithm("md5")
def test_login_cram_md5_blocked(self):
client, _ = self._setup(AuthHandler_CRAM_MD5)
self.assertIn('AUTH=CRAM-MD5', client.capabilities)
msg = re.escape("CRAM-MD5 authentication is not supported")
with self.assertRaisesRegex(imaplib.IMAP4.error, msg):
client.login_cram_md5("tim", b"tanstaaftanstaaf")

def test_aborted_authentication(self):
class MyServer(SimpleIMAPHandler):
def cmd_AUTHENTICATE(self, tag, args):
Expand Down
Loading
Loading