Skip to content

bpo-34844: logging.Formatter enhancement - Ensure styles and fmt matches in logging.Formatter #9703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Oct 15, 2018

Conversation

BNMetrics
Copy link
Contributor

@BNMetrics BNMetrics commented Oct 4, 2018

As discussed with @vsajip, I have made the following changes:

  • Added validate method in each format style class: StrFormatStyle, PercentStyle, StringTemplateStyle.
  • This method is called in the constructor of logging.Formatter class
  • Also re-raise the KeyError in the format method of each style class, so it would a bit clear that it's an error with the invalid format fields.

https://bugs.python.org/issue34844

@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Unfortunately our records indicate you have not signed the CLA. For legal reasons we need you to sign this before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

You can check yourself to see if the CLA has been received.

Thanks again for your contribution, we look forward to reviewing it!

@gvanrossum
Copy link
Member

I wouldn't worry about the Azure failure -- that's an experimental CI build that's still flaky, and it won't be held against you.

@@ -426,19 +429,44 @@ class PercentStyle(object):
def __init__(self, fmt):
self._fmt = fmt or self.default_format

@contextmanager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the thinking behind this. Why do we need a context manager for this? Please explain, See other comments further below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a context manager to avoid duplication, however I like your solution better. I'm going to make the change :)

self._validateFormat(r'(\%\([^%()]+\)[\s\d-]*[\w]+)')

def _validateFormat(self, expression):
matches = re.search(expression, self._fmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matches could mean it matches or it could mean all the matches. Suggest renaming to matched as it is used in a Boolean sense.

def format(self, record):
return self._fmt % record.__dict__
with self._verifyFields():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, each of the format() methods is a one-liner which basically does return ... <result of formatting operation>. If you added a context manager to avoid duplicating the exception handling code [which it would be good to do], then it would be simpler to do as follows:

BEFORE:

in each XXXStyle class:

    def format(self, record):
        return ... <result of formatting operation>

AFTER:

In each XXXStyle class:

rename `def format(self, record)`  to `def _format(self, record)`

and in PercentStyle only (the base class for the others), define:

    def format(self, record):
        try:
            return self._format(self, record)
        except KeyError as e:
            raise ValueError('Formatting field not found in record: %s' % e)

With this approach, you avoid the duplication, without the need for a context manager, importing `contextlib` etc.

def usesTime(self):
return self._fmt.find(self.asctime_search) >= 0

def validate(self):
self._validateFormat(r'(\%\([^%()]+\)[\s\d-]*[\w]+)')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you arrive at this pattern? It doesn't seem to match the specification given here. It might be OK to match just the %(name)s part of the pattern, or any valid pattern, but not a halfway house. For example, your pattern matches %(name)Z and %(name)_, which are not valid for a format field specifier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this does seem to match the example you have given. let me try and redo this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have gotten quite a few things wrong in this pattern. I thought something like this would be valid%(asctime)- 15s, but looking at the specifications, I definitely got it wrong

return self._fmt.format(**record.__dict__)

def validate(self):
self._validateFormat(r'(\{[^{}]+\})')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you arrive at this pattern? It matches {name-thing}, which is not a great name for a key even though str.format() allows it if passing via a generic dictionary.

Copy link
Contributor Author

@BNMetrics BNMetrics Oct 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable, I will exclude some special characters in the keys


def validate(self):
with self._verifyFields():
self._validateFormat(r'(\$\{[^${}]+\})')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you arrive at this pattern? It matches ${funcName} but fails to match $funcName which is valid for a $-formatting operation.

self.assertTrue(f.usesTime())
# Testing ValueError raised from mismatching format
self.assertRaises(ValueError, logging.Formatter, '{asctime}')
self.assertRaises(ValueError, logging.Formatter, '${message}')

def test_braces(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add an extra test test_format_validation that ensures that all valid kinds of format strings (for all styles) pass validation (paying attention to the detailed specs for format specifiers for each style), as well as checking that invalid formats fail with ValueError.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

And if you don't make the requested changes, you will be put in the comfy chair!

@BNMetrics
Copy link
Contributor Author

@vsajip Thanks for the review. I have made the changes suggested. If you could review again, that would be amazing! :)

class PercentStyle(object):

default_format = '%(message)s'
asctime_format = '%(asctime)s'
asctime_search = '%(asctime)'
validation_pattern = r'(\%\([^%()]+\)[#|0|\-|\s|+]?[\d]*[d|i|o|u|x|X|e|E|f|E|g|G|C|r|s|a|%]{1})'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still has some problems:

  • It is still not correct with respect to the spec - for example it doesn't handle the use of literal * to specify width and precision.
  • It's needlessly verbose - [d|i|o|u|x|X|e|E|f|E|g|G|C|r|s|a|%]{1} could be replaced with just [diouxefgcrsa] if compiled with re.I- excluding the % character, as that wouldn't make sense as part of a %(name) format specifier, and the {1} is redundant.
  • The bit between the parentheses - why not just use \w+?
  • When specifying character alternatives, you don't need a | - [#0+ -] would be preferable to [#|0|\-|\s|+], leaving the - at the end to avoid ambiguity, as otherwise - inside [...] has special meaning.
  • You didn't take up my point about using a compiled pattern. Did you not understand what I meant by that, or did you just forget it?

I suggest you just use re.compile(r'%\(\w+\)[#0+ -]?(\*|\d+)?(\.(\*|\d+))?[diouxefgcrsa]', re.I) as the validation_pattern.

def validate(self):
if not re.search(self.validation_pattern, self._fmt):
raise ValueError('Invalid format %(fmt)s for %(style)s style'
% {"fmt": self._fmt, "style": self.default_format[0]})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You really don't need to use named values in the format string in this context. I gave a specific example in the earlier comment beginning "This would only be needed in PercentStyle:" - did you miss it? Also, you're not using a compiled pattern, as suggested.

try:
return self._format(record)
except KeyError as e:
raise ValueError("Invalid formatting field %s" % e)
Copy link
Member

@vsajip vsajip Oct 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested earlier raise ValueError('Formatting field not found in record: %s' % e), as I thought that more informative. Do you not agree? "Invalid formatting field" suggests it might be improperly formatted, but the real problem is that it is not present in the record, and not how it's formatted (which your wording might be misinterpreted to mean).

class StrFormatStyle(PercentStyle):
default_format = '{message}'
asctime_format = '{asctime}'
asctime_search = '{asctime'
validation_pattern = r'(\{[^{!:}-]+(![r|s|a]{1})?(:[.]*[<|>|=]?[+|-|\s]?[\d]' \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my earlier comments about using tidy, correct and minimalist regex patterns and using a compiled pattern. Those comments also apply here.

return self._fmt.format(**record.__dict__)


class StringTemplateStyle(PercentStyle):
default_format = '${message}'
asctime_format = '${asctime}'
asctime_search = '${asctime}'
validation_pattern = r'(\$\{?[\w]+\}?)'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my earlier comments about using tidy, correct and minimalist regex patterns and using a compiled pattern. Those comments also apply here. For example, your pattern here allows the invalid values $foo} and ${foo.


from string import Template

from contextlib import contextmanager
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed - please remove.

@@ -544,6 +544,9 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
.. versionchanged:: 3.2
The *style* parameter was added.

.. versionchanged:: 3.8
Incorrect or mismatch style and ftm will raise a ``ValueError``. For
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ftm should be fmt.

class StrFormatStyle(PercentStyle):
default_format = '{message}'
asctime_format = '{asctime}'
asctime_search = '{asctime'
validation_pattern = re.compile(r'\{[^{!:}-]+(![rsa])?(:(.)?[<>=]?[+ -]?[#0]?[\d]*,?[\.\d]*)?[bcdefgnosx%]?\}',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern still needs work 😞 For example, it passes {{levelname}} which contains no format specifiers: the doubling up of {{ means it is formatted as a single {. So '{{levelname}}'.format(levelname='foo') returns {levelname}, and we don't handle this. It's a little tricky: let me think about that part.

It also passes things like {leve*ln/ame} which doesn't seem right. I think you need to follow the spec more closely in the regex, even though it might feel like a pain to do. And these error cases above need to be added to the tests so that we can verify they are caught.

More problems:

  • (.)? is best replaced by .?.
  • We catch <, > and = in the align part, but not ^.
  • According to the spec, that should be followed by #?0?, not [#0]? (i.e. one could have both chars present)
  • [\d]* is equivalent to \d*, but noisier.
  • We don't seem to allow _ as a grouping specifier.
  • We perhaps need to handle {fieldname:{width}.{precision}} which is a valid specifier (note the }} at the end, which would normally be escaped - I'm thinking that maybe a regex won't do the job after all 😭).
  • The precision specifier should be (\.\d+)?, not [\.\d]*.

@@ -438,7 +442,7 @@ def validate(self):
self._invalid_raise()

def _invalid_raise(self, additional_message=None):
msg = 'Invalid format %s for %s style' % (self._fmt, self.default_format[0])
msg = "Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use 'Invalid format %r for %r style' to get the single quotes in.

@@ -438,7 +442,7 @@ def validate(self):
self._invalid_raise()

def _invalid_raise(self, additional_message=None):
msg = 'Invalid format %s for %s style' % (self._fmt, self.default_format[0])
msg = "Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0])
if additional_message:
msg = "%s, %s" % (msg, additional_message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to use single quotes where possible.

@@ -458,29 +462,38 @@ class StrFormatStyle(PercentStyle):
asctime_format = '{asctime}'
asctime_search = '{asctime'
# This is only for validating the format_spec
validation_pattern = re.compile(r"^((.)?[<>=]?[+ -]?[#0]?[\d]*,?[\.\d]*)?[bcdefgnosx%]?$", re.I)
validation_pattern = re.compile(r"^(.?[<>=^]?[+ -]?#?0?[\d]*,?(\.[\d]*)?)?[bcdefgnosx%]?$", re.I)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a bit of a think about this whole area - I think both brace and dollar validation need a bit more work. I put my thoughts into this Gist - see what you think and if you can adapt some of it. The test cases in there might be useful, too - and you could try adding to them using combinations I haven't tried.

I also think maybe we should add a validate= keyword parameter to Formatter.__init__, which defaults to True but which a user can set to False to disable validation, as a workaround for if we miss something.

It means that the validation_pattern idea I had was inadequate - for %-formatting a single pattern seems OK, for {-formatting we need the two-step approach you came up with, and I also realised that we hadn't catered for the no-fields case for $-formatting. But there's little point in having a uniform validate() that just relies on one pattern, as that won't fly. 😞

if not self.validation_pattern.search(self._fmt):
self._invalid_raise()

def _invalid_raise(self, additional_message=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would prefer the name _raise_invalid as the verb should come first, generally.

Copy link
Member

@vsajip vsajip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one comment - looks good, otherwise!

@@ -3597,6 +3760,7 @@ def test_formatting(self):
'deliberate mistake'))
self.assertTrue(r.stack_info.startswith('Stack (most recent '
'call last):\n'))
print(r.stack_info)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably didn't mean to leave this line in? Was it there just for debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! removing this now!

Copy link
Member

@vsajip vsajip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just remembered one more thing - sorry, I completely forgot all about it up to now! We need a way to allow the validate flag in Formatter configuration (dictConfig()). The relevant code is here and we need to cater for:

  • Getting the validate value from the configuration dict, defaulting to True if not specified
  • Using it in the call to instantiate the formatter, but only in the case where it's our logging.Formatter class and not a custom user-defined class (which won't know about the new keyword argument, and could possibly raise a TypeError if called with it).

@@ -226,6 +226,11 @@ otherwise, the context is used to determine what to instantiate.
(with defaults of ``None``) and these are used to construct a
:class:`~logging.Formatter` instance.

.. versionchanged:: 3.8
a ``validate`` key (with defaults of ``True``) can be added into
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaults -> a default

.. versionchanged:: 3.8
a ``validate`` key (with defaults of ``True``) can be added into
the ``formatters`` section of the configuring dict, this is to
validate the correct format.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate the correct format -> validate the format

@@ -33,6 +33,7 @@
import sys
import threading
import traceback
from inspect import signature
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC importing inspect can be expensive, so consider only importing it if and where needed. If it is kept here, note that it needs to go in the above list in alphabetical order (I know other parts of logging don't follow that, but this module does). We may not even need it - see comment below.

return result

def _has_validate_param(self, config, formatter_callable):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be needed, see above.

@@ -666,13 +667,29 @@ def configure_formatter(self, config):
dfmt = config.get('datefmt', None)
style = config.get('style', '%')
cname = config.get('class', None)
validate = config.get('validate', True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be overthinking this, but the logic in this area should perhaps be:

if not cname:
    c = logging.Formatter
else:
    c = _resolve(cname)  # this might actually resolve to logging.Formatter

if c is logging.Formatter:
    # we know c will accept the validate parameter
    use_validate = True
    validate = config.get('validate', True)
else:
    # we don't yet know if c will accept a validate parameter.
    use_validate = False
    # Here, we could go to some trouble to determine whether there's a validate
    # or kwargs parameter, as you've done. But we have to take care:  there are
    # a number of things to consider. We currently call c with fmt, dfmt, style
    # arguments. So we expect c to be a class which inherits from
    # logging.Formatter and either doesn't redefine __init__, or does redefine
    # it. In the first case, we'd be safe to set use_validate to True, but in
    # the second, we wouldn't, because we don't know if this a Formatter
    # subclass written after 3.8 was released - when the developer of it would
    # be expected to know about the validate parameter - or whether it was a
    # class designed before 3.8, when they wouldn't be expected to know about
    # it. If it's specified in the config, then they know about it and we can
    # assume it's a post-3.8 design, so we can pass the configured value;
    # otherwise, we have as yet no idea if the redefined __init__ will accept
    # validate. We could dig into its signature to see if we could figure it
    # out, but there's no guarantee it will be *our* validate parameter - even
    # if we can figure out that it's expected to be a bool, we have no idea
    # how a True value would be interpreted. So it's safest not to pass it.

    # In the case that c is a class which doesn't even inherit from Formatter,
    # we have no idea what its __init__ does, though we do know that it has to
    # take (fmt, dfmt, style) parameters, otherwise it wouldn't work now.
    # Thus:

    if issubclass(c, logging.Formatter):
        if '__init__' not in c.__dict__:  # not redefined
            use_validate = True
        elif 'validate' in config:  # user has specified it, so OK to use
            use_validate = True
        # else:
            # if we decided to implement signature checking, we would import
            # inspect here and introspect the signature. Note that you couldn't
            # rely on a parameter literally named 'kwargs' - someone could have
            # used **kw, for example. You'd have to rely on there being a
            # specific parameter p such that p.kind == inspect._VAR_KEYWORD.

if use_validate :
    result = c(fmt, dfmt, style)
else:
    result = c(fmt, dfmt, style, validate)

@@ -2125,6 +2125,10 @@ def test_warnings_no_handlers(self):
def formatFunc(format, datefmt=None):
return logging.Formatter(format, datefmt)

class myCustomFormatter:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about formatter classes that don't inherit from logging.Formatter.

@@ -544,6 +544,9 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
.. versionchanged:: 3.2
The *style* parameter was added.

.. versionchanged:: 3.8
Incorrect or mismatch style and fmt will raise a ``ValueError``. For
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mismatch -> mismatched

self.apply_config(config)
handler = logging.getLogger("my_test_logger_custom_formatter").handlers[0]
self.assertIsInstance(handler.formatter, ExceptionFormatter)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be whitespace in this line and line 3311 causing make patchcheck to fail in CI. Please run make patchcheck locally that will fix this for you automatically. Patch output of make patchcheck for reference

diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
index bfd801f600..9802955e6a 100644
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -3305,10 +3305,10 @@ class ConfigDictTest(BaseTest):
         self.apply_config(config)
         handler = logging.getLogger("my_test_logger_custom_formatter").handlers[0]
         self.assertIsInstance(handler.formatter, ExceptionFormatter)
-
+
     def test_custom_formatter_class_with_validate3(self):
         self.assertRaises(ValueError, self.apply_config, self.custom_formatter_class_validate3)
-
+
     def test_custom_formatter_function_with_validate(self):
         self.assertRaises(ValueError, self.apply_config, self.custom_formatter_with_function)

Thanks

# Identifying if we need to pass in the validate parameter to the formatter
# - The default in logging.Formatter is True, so we won't need to pass in
# if "validate" key is not in the config
use_validate = False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, much simpler than my overthinking! But that's not right, because if the user specified False in the config, then config.get('validate') would return False, so use_validate would not be set to True and the user's False would never be passed to the call.

IMO you could lose the use_validate altogether and simplify even further to:

if 'validate' in config:  # if user hasn't mentioned it, the default will be fine
    result = c(fmt, dfmt, style, config['validate'])
else:
    result = c(fmt, dfmt, style)

Notice my stubborn sticking to single quotes 😉

@@ -544,6 +544,9 @@ The useful mapping keys in a :class:`LogRecord` are given in the section on
.. versionchanged:: 3.2
The *style* parameter was added.

.. versionchanged:: 3.8
Incorrect or mismatched style and fmt will raise a ``ValueError``. For
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insert "The validate parameter was added." before "Incorrect or mismatched style..."

@vsajip vsajip merged commit 18fb1fb into python:master Oct 15, 2018
@pablogsal
Copy link
Member

pablogsal commented Oct 16, 2018

This commit fails to build in the x86 Gentoo Refleaks 3.x:

https://buildbot.python.org/all/#/builders/1/builds/377/steps/4/logs/stdio

Opened https://bugs.python.org/issue34997. I will try to debug it later this week.

vstinner added a commit that referenced this pull request Oct 16, 2018
…ormat string matches in logging.Formatter (GH-9703)"

This reverts commit 18fb1fb.
@vstinner
Copy link
Member

This change broke a buildbot https://bugs.python.org/issue34997

I suggest to revert the change until someone comes with a fix.

@pablogsal
Copy link
Member

@vstinner I opened #9913 to fix this.

CuriousLearner added a commit to CuriousLearner/cpython that referenced this pull request Oct 18, 2018
* master: (621 commits)
  Update opcode.h header comment to mention the source data file (pythonGH-9935)
  bpo-34936: Fix TclError in tkinter.Spinbox.selection_element(). (pythonGH-9760)
  Updated documentation on logging.debug(). (pythonGH-9946)
  bpo-34765: Update the install-sh file (pythonGH-9592)
  bpo-35008: Fix possible leaks in Element.__setstate__(). (pythonGH-9924)
  bpo-35011: Restore use of pyexpatns.h in libexpat (pythonGH-9939)
  bpo-24658: Fix read/write greater than 2 GiB on macOS (pythonGH-1705)
  Add missing comma to wsgiref doc (pythonGH-9932)
  bpo-23420: Verify the value of '-s' when execute the CLI of cProfile (pythonGH-9925)
  Doc: Fix is_prime (pythonGH-9909)
  In email docs, correct spelling of foregoing (python#9856)
  In email.parser in message_from_bytes, update `strict` to `policy` (python#9854)
  bpo-34997: Fix test_logging.ConfigDictTest.test_out_of_order (pythonGH-9913)
  Added CLI starter example to logging cookbook. (pythonGH-9910)
  bpo-34783: Fix test_nonexisting_script() (pythonGH-9896)
  bpo-23554: Change echo server example class name from EchoServerClientProtocol to EchoServerProtocol (pythonGH-9859)
  bpo-34989: python-gdb.py: fix current_line_num() (pythonGH-9889)
  Stop using deprecated logging API in Sphinx suspicious checker (pythonGH-9875)
  fix dangling keyfunc examples in documentation of heapq and sorted (python#1432)
  bpo-34844: logging.Formatter enhancement - Ensure style and format string matches in logging.Formatter  (pythonGH-9703)
  ...
CuriousLearner added a commit to CuriousLearner/cpython that referenced this pull request Oct 18, 2018
* master: (1787 commits)
  Update opcode.h header comment to mention the source data file (pythonGH-9935)
  bpo-34936: Fix TclError in tkinter.Spinbox.selection_element(). (pythonGH-9760)
  Updated documentation on logging.debug(). (pythonGH-9946)
  bpo-34765: Update the install-sh file (pythonGH-9592)
  bpo-35008: Fix possible leaks in Element.__setstate__(). (pythonGH-9924)
  bpo-35011: Restore use of pyexpatns.h in libexpat (pythonGH-9939)
  bpo-24658: Fix read/write greater than 2 GiB on macOS (pythonGH-1705)
  Add missing comma to wsgiref doc (pythonGH-9932)
  bpo-23420: Verify the value of '-s' when execute the CLI of cProfile (pythonGH-9925)
  Doc: Fix is_prime (pythonGH-9909)
  In email docs, correct spelling of foregoing (python#9856)
  In email.parser in message_from_bytes, update `strict` to `policy` (python#9854)
  bpo-34997: Fix test_logging.ConfigDictTest.test_out_of_order (pythonGH-9913)
  Added CLI starter example to logging cookbook. (pythonGH-9910)
  bpo-34783: Fix test_nonexisting_script() (pythonGH-9896)
  bpo-23554: Change echo server example class name from EchoServerClientProtocol to EchoServerProtocol (pythonGH-9859)
  bpo-34989: python-gdb.py: fix current_line_num() (pythonGH-9889)
  Stop using deprecated logging API in Sphinx suspicious checker (pythonGH-9875)
  fix dangling keyfunc examples in documentation of heapq and sorted (python#1432)
  bpo-34844: logging.Formatter enhancement - Ensure style and format string matches in logging.Formatter  (pythonGH-9703)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants