-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
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
Conversation
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! |
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. |
Lib/logging/__init__.py
Outdated
@@ -426,19 +429,44 @@ class PercentStyle(object): | |||
def __init__(self, fmt): | |||
self._fmt = fmt or self.default_format | |||
|
|||
@contextmanager |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
Lib/logging/__init__.py
Outdated
self._validateFormat(r'(\%\([^%()]+\)[\s\d-]*[\w]+)') | ||
|
||
def _validateFormat(self, expression): | ||
matches = re.search(expression, self._fmt) |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
def format(self, record): | ||
return self._fmt % record.__dict__ | ||
with self._verifyFields(): |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
def usesTime(self): | ||
return self._fmt.find(self.asctime_search) >= 0 | ||
|
||
def validate(self): | ||
self._validateFormat(r'(\%\([^%()]+\)[\s\d-]*[\w]+)') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Lib/logging/__init__.py
Outdated
return self._fmt.format(**record.__dict__) | ||
|
||
def validate(self): | ||
self._validateFormat(r'(\{[^{}]+\})') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Lib/logging/__init__.py
Outdated
|
||
def validate(self): | ||
with self._verifyFields(): | ||
self._validateFormat(r'(\$\{[^${}]+\})') |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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
.
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 And if you don't make the requested changes, you will be put in the comfy chair! |
@vsajip Thanks for the review. I have made the changes suggested. If you could review again, that would be amazing! :) |
Lib/logging/__init__.py
Outdated
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})' |
There was a problem hiding this comment.
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 withre.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
.
Lib/logging/__init__.py
Outdated
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]}) |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
try: | ||
return self._format(record) | ||
except KeyError as e: | ||
raise ValueError("Invalid formatting field %s" % e) |
There was a problem hiding this comment.
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).
Lib/logging/__init__.py
Outdated
class StrFormatStyle(PercentStyle): | ||
default_format = '{message}' | ||
asctime_format = '{asctime}' | ||
asctime_search = '{asctime' | ||
validation_pattern = r'(\{[^{!:}-]+(![r|s|a]{1})?(:[.]*[<|>|=]?[+|-|\s]?[\d]' \ |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
return self._fmt.format(**record.__dict__) | ||
|
||
|
||
class StringTemplateStyle(PercentStyle): | ||
default_format = '${message}' | ||
asctime_format = '${asctime}' | ||
asctime_search = '${asctime}' | ||
validation_pattern = r'(\$\{?[\w]+\}?)' |
There was a problem hiding this comment.
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
.
Lib/logging/__init__.py
Outdated
|
||
from string import Template | ||
|
||
from contextlib import contextmanager |
There was a problem hiding this comment.
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.
Doc/library/logging.rst
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ftm
should be fmt
.
Lib/logging/__init__.py
Outdated
class StrFormatStyle(PercentStyle): | ||
default_format = '{message}' | ||
asctime_format = '{asctime}' | ||
asctime_search = '{asctime' | ||
validation_pattern = re.compile(r'\{[^{!:}-]+(![rsa])?(:(.)?[<>=]?[+ -]?[#0]?[\d]*,?[\.\d]*)?[bcdefgnosx%]?\}', |
There was a problem hiding this comment.
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]*
.
Lib/logging/__init__.py
Outdated
@@ -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]) |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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.
Lib/logging/__init__.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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. 😞
Lib/logging/__init__.py
Outdated
if not self.validation_pattern.search(self._fmt): | ||
self._invalid_raise() | ||
|
||
def _invalid_raise(self, additional_message=None): |
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
Lib/test/test_logging.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! removing this now!
There was a problem hiding this 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 toTrue
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 aTypeError
if called with it).
Doc/library/logging.config.rst
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaults
-> a default
Doc/library/logging.config.rst
Outdated
.. 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. |
There was a problem hiding this comment.
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
Lib/logging/config.py
Outdated
@@ -33,6 +33,7 @@ | |||
import sys | |||
import threading | |||
import traceback | |||
from inspect import signature |
There was a problem hiding this comment.
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.
Lib/logging/config.py
Outdated
return result | ||
|
||
def _has_validate_param(self, config, formatter_callable): |
There was a problem hiding this comment.
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.
Lib/logging/config.py
Outdated
@@ -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) |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
.
Doc/library/logging.rst
Outdated
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mismatch
-> mismatched
Lib/test/test_logging.py
Outdated
self.apply_config(config) | ||
handler = logging.getLogger("my_test_logger_custom_formatter").handlers[0] | ||
self.assertIsInstance(handler.formatter, ExceptionFormatter) | ||
|
There was a problem hiding this comment.
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
…mplified process without importing inspect module
Lib/logging/config.py
Outdated
# 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 |
There was a problem hiding this comment.
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 😉
Doc/library/logging.rst
Outdated
@@ -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 |
There was a problem hiding this comment.
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..."
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. |
This change broke a buildbot https://bugs.python.org/issue34997 I suggest to revert the change until someone comes with a fix. |
* 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) ...
* 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) ...
As discussed with @vsajip, I have made the following changes:
validate
method in each format style class:StrFormatStyle
,PercentStyle
,StringTemplateStyle
.KeyError
in theformat
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