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
24 changes: 18 additions & 6 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
Retrieving source code
----------------------

.. function:: getdoc(object, *, inherit_class_doc=True, fallback_to_class_doc=True)
.. function:: getdoc(object, *, inherit_class_doc=True, fallback_to_class_doc=True, dedent=True)

Get the documentation string for an object, cleaned up with :func:`cleandoc`.
Get the documentation string for an object, cleaned up with :func:`cleandoc`
(with the same meaning of *dedent*).
If the documentation string for an object is not provided:

* if the object is a class and *inherit_class_doc* is true (by default),
Expand All @@ -730,6 +731,9 @@ Retrieving source code
Documentation strings on :class:`~functools.cached_property`
objects are now inherited if not overridden.

.. versionchanged:: next
Added the *dedent* parameter.


.. function:: getcomments(object)

Expand Down Expand Up @@ -791,15 +795,23 @@ Retrieving source code
former.


.. function:: cleandoc(doc)
.. function:: cleandoc(doc, *, dedent=True)

Clean up indentation from docstrings that are indented to line up with blocks
of code.

All leading whitespace is removed from the first line. Any leading whitespace
that can be uniformly removed from the second line onwards is removed. Empty
lines at the beginning and end are subsequently removed. Also, all tabs are
expanded to spaces.
that can be uniformly removed from the second line onwards is removed, unless
*dedent* is false. Empty lines at the beginning and end are subsequently
removed. Also, all tabs are expanded to spaces.

Since Python 3.13 the compiler removes the indentation of docstrings, so
*dedent* only affects documentation strings which are not written as
docstrings in the source code, like those generated by Argument Clinic,
where the indentation is meaningful.

.. versionchanged:: next
Added the *dedent* parameter.


.. _inspect-signature-object:
Expand Down
4 changes: 2 additions & 2 deletions Doc/library/subprocess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ underlying :class:`Popen` interface can be used directly.

.. attribute:: returncode

Exit status of the child process. If the process exited due to a
signal, this will be the negative signal number.
Exit status of the child process, an integer. If the process
exited due to a signal, this will be the negative signal number.

.. attribute:: cmd

Expand Down
26 changes: 26 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,32 @@ The :mod:`!test.support.os_helper` module provides support for os tests.
wrapped with a wait loop that checks for the existence of the file.


.. decorator:: with_source_date_epoch(*, epoch=123456789)

A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
environment variable set to *epoch*.


.. decorator:: without_source_date_epoch

A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
environment variable unset.


.. class:: SourceDateEpochTestMeta

Metaclass wrapping all test methods of the class with
:func:`with_source_date_epoch` if the *source_date_epoch* keyword class
argument is true, or with :func:`without_source_date_epoch` otherwise.
For example::

class TestsWithSourceEpoch(Tests,
metaclass=SourceDateEpochTestMeta,
source_date_epoch=True):
pass



:mod:`!test.support.import_helper` --- Utilities for import tests
=================================================================

Expand Down
23 changes: 13 additions & 10 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,12 +793,14 @@ def _getowndoc(obj):
except AttributeError:
return None

def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True):
def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True,
dedent=True):
"""Get the documentation string for an object.

All tabs are expanded to spaces. To clean up docstrings that are
indented to line up with blocks of code, any whitespace than can be
uniformly removed from the second line onwards is removed."""
uniformly removed from the second line onwards is removed, unless
dedent is false."""
if fallback_to_class_doc:
try:
doc = object.__doc__
Expand All @@ -813,22 +815,23 @@ def getdoc(object, *, fallback_to_class_doc=True, inherit_class_doc=True):
return None
if not isinstance(doc, str):
return None
return cleandoc(doc)
return cleandoc(doc, dedent=dedent)

def cleandoc(doc):
def cleandoc(doc, *, dedent=True):
"""Clean up indentation from docstrings.

Any whitespace that can be uniformly removed from the second line
onwards is removed."""
onwards is removed, unless dedent is false."""
lines = doc.expandtabs().split('\n')

# Find minimum indentation of any non-blank lines after first line.
margin = sys.maxsize
for line in lines[1:]:
content = len(line.lstrip(' '))
if content:
indent = len(line) - content
margin = min(margin, indent)
if dedent:
for line in lines[1:]:
content = len(line.lstrip(' '))
if content:
indent = len(line) - content
margin = min(margin, indent)
# Remove indentation.
if lines:
lines[0] = lines[0].lstrip(' ')
Expand Down
5 changes: 4 additions & 1 deletion Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@ def pathdirs():
return dirs

def _getdoc(object):
# Docstrings written in the source are dedented by the compiler; the
# indentation of generated docstrings is meaningful.
return inspect.getdoc(object,
fallback_to_class_doc=False,
inherit_class_doc=False)
inherit_class_doc=False,
dedent=False)

def getdoc(object):
"""Get the doc string or comments for an object."""
Expand Down
6 changes: 3 additions & 3 deletions Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,16 @@ def __init__(self, returncode, cmd, output=None, stderr=None):
self.stderr = stderr

def __str__(self):
if self.returncode and self.returncode < 0:
if isinstance(self.returncode, int) and self.returncode < 0:
try:
return "Command %r died with %r." % (
self.cmd, signal.Signals(-self.returncode))
except ValueError:
return "Command %r died with unknown signal %d." % (
self.cmd, -self.returncode)
else:
return "Command %r returned non-zero exit status %d." % (
self.cmd, self.returncode)
return (f"Command {self.cmd!r} returned non-zero "
f"exit status {self.returncode}.")

@property
def stdout(self):
Expand Down
7 changes: 4 additions & 3 deletions Lib/test/dtracedata/call_stack.stp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function basename:string(path:string)
return last_token;
}

probe process.mark("function__entry")
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry")
{
funcname = user_string($arg2);

Expand All @@ -19,7 +19,8 @@ probe process.mark("function__entry")
}
}

probe process.mark("function__entry"), process.mark("function__return")
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry"),
@PYTHON_SYSTEMTAP_PROBE@("function__return")
{
filename = user_string($arg1);
funcname = user_string($arg2);
Expand All @@ -31,7 +32,7 @@ probe process.mark("function__entry"), process.mark("function__return")
}
}

probe process.mark("function__return")
probe @PYTHON_SYSTEMTAP_PROBE@("function__return")
{
funcname = user_string($arg2);

Expand Down
7 changes: 4 additions & 3 deletions Lib/test/dtracedata/gc.stp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
global tracing

probe process.mark("function__entry")
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry")
{
funcname = user_string($arg2);

Expand All @@ -9,14 +9,15 @@ probe process.mark("function__entry")
}
}

probe process.mark("gc__start"), process.mark("gc__done")
probe @PYTHON_SYSTEMTAP_PROBE@("gc__start"),
@PYTHON_SYSTEMTAP_PROBE@("gc__done")
{
if (tracing) {
printf("%d\t%s:%ld\n", gettimeofday_us(), $$name, $arg1);
}
}

probe process.mark("function__return")
probe @PYTHON_SYSTEMTAP_PROBE@("function__return")
{
funcname = user_string($arg2);

Expand Down
43 changes: 43 additions & 0 deletions Lib/test/support/os_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import collections.abc
import contextlib
import errno
import functools
import logging
import os
import re
Expand Down Expand Up @@ -806,6 +807,48 @@ def __exit__(self, *ignore_exc):
os.environ = self._environ


def without_source_date_epoch(fxn):
"""Runs function with SOURCE_DATE_EPOCH unset."""
@functools.wraps(fxn)
def wrapper(*args, **kwargs):
with EnvironmentVarGuard() as env:
env.unset('SOURCE_DATE_EPOCH')
return fxn(*args, **kwargs)
return wrapper


_MISSING = sentinel("MISSING")

def with_source_date_epoch(fxn=_MISSING, *, epoch=123456789):
"""Runs function with SOURCE_DATE_EPOCH set to *epoch*."""
if fxn is _MISSING:
return functools.partial(with_source_date_epoch, epoch=epoch)

@functools.wraps(fxn)
def wrapper(*args, **kwargs):
with EnvironmentVarGuard() as env:
env['SOURCE_DATE_EPOCH'] = str(epoch)
return fxn(*args, **kwargs)
return wrapper


# Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
class SourceDateEpochTestMeta(type(unittest.TestCase)):
def __new__(mcls, name, bases, dct, *, source_date_epoch):
cls = super().__new__(mcls, name, bases, dct)

for attr in dir(cls):
if attr.startswith('test_'):
meth = getattr(cls, attr)
if source_date_epoch:
wrapper = with_source_date_epoch(meth)
else:
wrapper = without_source_date_epoch(meth)
setattr(cls, attr, wrapper)

return cls


try:
if support.MS_WINDOWS:
import ctypes
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
from test import support
from test.support import os_helper
from test.support import script_helper
from test.test_py_compile import without_source_date_epoch
from test.test_py_compile import SourceDateEpochTestMeta
from test.support.os_helper import without_source_date_epoch
from test.support.os_helper import SourceDateEpochTestMeta
from test.support.os_helper import FakePath


Expand Down
Loading
Loading