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 .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -573,9 +573,9 @@ Lib/shutil.py @giampaolo
Lib/test/test_shutil.py @giampaolo

# Site
Lib/site.py @FFY00
Lib/test/test_site.py @FFY00
Doc/library/site.rst @FFY00
Lib/site.py @FFY00 @warsaw
Lib/test/test_site.py @FFY00 @warsaw
Doc/library/site.rst @FFY00 @warsaw

# string.templatelib
Doc/library/string.templatelib.rst @lysnikolaou @AA-Turner
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ functools
* Calling the Python implementation of :func:`functools.reduce` with *function*
or *sequence* as keyword arguments has been deprecated since Python 3.14.

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.

symtable
--------

Expand Down
23 changes: 1 addition & 22 deletions Lib/logging/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -865,28 +865,7 @@ def configure_handler(self, config):
else:
factory = klass
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
# When deprecation ends for using the 'strm' parameter, remove the
# "except TypeError ..."
try:
result = factory(**kwargs)
except TypeError as te:
if "'stream'" not in str(te):
raise
#The argument name changed from strm to stream
#Retry with old name.
#This is so that code can be used with older Python versions
#(e.g. by Django)
kwargs['strm'] = kwargs.pop('stream')
result = factory(**kwargs)

import warnings
warnings.warn(
"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.",
DeprecationWarning,
stacklevel=2,
)
result = factory(**kwargs)
if formatter:
result.setFormatter(formatter)
if level is not None:
Expand Down
16 changes: 8 additions & 8 deletions Lib/rlcompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ def attr_matches(self, text):
if (word[:n] == attr and
not (noprefix and word[:n+1] == noprefix)):
match = "%s.%s" % (expr, word)
if isinstance(getattr(type(thisobject), word, None),
property):
# bpo-44752: thisobject.word is a method decorated by
# `@property`. What follows applies a postfix if
# thisobject.word is callable, but know we know that
# this is not callable (because it is a property).
# Also, getattr(thisobject, word) will evaluate the
# property method, which is not desirable.

class_attr = getattr(type(thisobject), word, None)
if isinstance(
class_attr,
(property, types.GetSetDescriptorType, types.MemberDescriptorType)
) or (hasattr(class_attr, '__get__') and not callable(class_attr)):
# Avoid evaluating descriptors, which could run
# arbitrary code or raise exceptions.
matches.append(match)
continue

Expand Down
68 changes: 37 additions & 31 deletions Lib/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,42 +387,48 @@ def addsitedir(sitedir, known_paths=None, *, defer_processing_start_files=False)
else:
reset = False
sitedir, sitedircase = makepath(sitedir)
if not sitedircase in known_paths:
sys.path.append(sitedir) # Add path component

# If the normcase'd new sitedir isn't already known, append it to
# sys.path, keep a record of it, and process all .pth and .start files
# found in that directory. If the new sitedir is known, be sure not
# to process all of those twice! gh-75723
if sitedircase not in known_paths:
sys.path.append(sitedir)
known_paths.add(sitedircase)
try:
names = os.listdir(sitedir)
except OSError:
return

# The following phases are defined by PEP 829.
# Phases 1-3: Read .pth files, accumulating paths and import lines.
pth_names = sorted(
name for name in names
if name.endswith(".pth") and not name.startswith(".")
)
for name in pth_names:
_read_pth_file(sitedir, name, known_paths)

# Phases 6-7: Discover .start files and accumulate their entry points.
# Import lines from .pth files with a matching .start file are discarded
# at flush time by _exec_imports().
start_names = sorted(
name for name in names
if name.endswith(".start") and not name.startswith(".")
)
for name in start_names:
_read_start_file(sitedir, name)
try:
names = os.listdir(sitedir)
except OSError:
return

# The following phases are defined by PEP 829.
# Phases 1-3: Read .pth files, accumulating paths and import lines.
pth_names = sorted(
name for name in names
if name.endswith(".pth") and not name.startswith(".")
)
for name in pth_names:
_read_pth_file(sitedir, name, known_paths)

# Phases 6-7: Discover .start files and accumulate their entry points.
# Import lines from .pth files with a matching .start file are discarded
# at flush time by _exec_imports().
start_names = sorted(
name for name in names
if name.endswith(".start") and not name.startswith(".")
)
for name in start_names:
_read_start_file(sitedir, name)

# Generally, when addsitedir() is called explicitly, we'll want to process
# all the startup file data immediately. However, when called through
# main(), we'll want to batch up all the startup file processing. main()
# will set this flag to True to defer processing.
if not defer_processing_start_files:
process_startup_files()
# Generally, when addsitedir() is called explicitly, we'll want to process
# all the startup file data immediately. However, when called through
# main(), we'll want to batch up all the startup file processing. main()
# will set this flag to True to defer processing.
if not defer_processing_start_files:
process_startup_files()

if reset:
known_paths = None
return None

return known_paths

Expand Down
18 changes: 6 additions & 12 deletions Lib/test/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3297,12 +3297,11 @@ def format(self, record):
}
}

# Remove when deprecation ends.
class DeprecatedStrmHandler(logging.StreamHandler):
class StrmHandler(logging.StreamHandler):
def __init__(self, strm=None):
super().__init__(stream=strm)

config_custom_handler_with_deprecated_strm_arg = {
config_custom_handler_with_removed_strm_arg = {
"version": 1,
"formatters": {
"form1": {
Expand All @@ -3311,7 +3310,7 @@ def __init__(self, strm=None):
},
"handlers": {
"hand1": {
"class": DeprecatedStrmHandler,
"class": StrmHandler,
"formatter": "form1",
"level": "NOTSET",
"stream": "ext://sys.stdout",
Expand Down Expand Up @@ -3417,14 +3416,9 @@ def test_config5_ok(self):
self.test_config1_ok(config=self.config5)
self.check_handler('hand1', CustomHandler)

def test_deprecation_warning_custom_handler_with_strm_arg(self):
msg = (
"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."
)
with self.assertWarnsRegex(DeprecationWarning, msg):
self.test_config1_ok(config=self.config_custom_handler_with_deprecated_strm_arg)
def test_removed_strm_arg(self):
with self.assertRaisesRegex(ValueError, 'hand1'):
self.apply_config(self.config_custom_handler_with_removed_strm_arg)

def test_config6_failure(self):
self.assertRaises(Exception, self.apply_config, self.config6)
Expand Down
52 changes: 52 additions & 0 deletions Lib/test/test_rlcompleter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
from unittest.mock import patch
import builtins
import types
import rlcompleter
from test.support import MISSING_C_DOCSTRINGS

Expand Down Expand Up @@ -135,6 +136,57 @@ def bar(self):
self.assertEqual(completer.complete('f.b', 0), 'f.bar')
self.assertFalse(f.property_called)

def test_released_memoryview_completion_works(self):
mv = memoryview(b"abc")
mv.release()

self.assertIsInstance(type(mv).shape, types.GetSetDescriptorType)
self.assertIsInstance(type(mv).strides, types.GetSetDescriptorType)

completer = rlcompleter.Completer(dict(mv=mv))
matches = completer.attr_matches('mv.')

# These are getset descriptors on memoryview and should be completed
# without evaluating the released-memoryview getters.
self.assertIn('mv.shape', matches)
self.assertIn('mv.strides', matches)

def test_member_descriptor_not_evaluated(self):
class Foo:
__slots__ = ("boom",)
boom_accesses = 0

def __getattribute__(self, name):
if name == "boom":
type(self).boom_accesses += 1
raise RuntimeError("boom access should be skipped")
return super().__getattribute__(name)

self.assertIsInstance(Foo.boom, types.MemberDescriptorType)

completer = rlcompleter.Completer(dict(f=Foo()))
matches = completer.attr_matches('f.')
self.assertIn('f.boom', matches)
self.assertEqual(Foo.boom_accesses, 0)

def test_raising_descriptor_completion_works(self):
class ExplodingDescriptor:
def __init__(self):
self.instance_get_calls = 0

def __get__(self, obj, owner):
if obj is None:
return self
self.instance_get_calls += 1
raise RuntimeError("descriptor getter exploded")

class Foo:
boom = ExplodingDescriptor()

completer = rlcompleter.Completer(dict(f=Foo()))
matches = completer.attr_matches('f.')
self.assertIn('f.boom', matches)
self.assertEqual(Foo.boom.instance_get_calls, 0)

def test_uncreated_attr(self):
# Attributes like properties and slots should be completed even when
Expand Down
Loading
Loading