Skip to content

Commit 8a434f8

Browse files
committed
Merge remote-tracking branch 'upstream/main' into pythongh-94498
2 parents 4fd3836 + fd76eb5 commit 8a434f8

38 files changed

+885
-394
lines changed

Doc/howto/logging-cookbook.rst

+222
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,228 @@ which, when run, produces something like:
714714
2010-09-06 22:38:15,301 d.e.f DEBUG IP: 123.231.231.123 User: fred A message at DEBUG level with 2 parameters
715715
2010-09-06 22:38:15,301 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters
716716
717+
Use of ``contextvars``
718+
----------------------
719+
720+
Since Python 3.7, the :mod:`contextvars` module has provided context-local storage
721+
which works for both :mod:`threading` and :mod:`asyncio` processing needs. This type
722+
of storage may thus be generally preferable to thread-locals. The following example
723+
shows how, in a multi-threaded environment, logs can populated with contextual
724+
information such as, for example, request attributes handled by web applications.
725+
726+
For the purposes of illustration, say that you have different web applications, each
727+
independent of the other but running in the same Python process and using a library
728+
common to them. How can each of these applications have their own log, where all
729+
logging messages from the library (and other request processing code) are directed to
730+
the appropriate application's log file, while including in the log additional
731+
contextual information such as client IP, HTTP request method and client username?
732+
733+
Let's assume that the library can be simulated by the following code:
734+
735+
.. code-block:: python
736+
737+
# webapplib.py
738+
import logging
739+
import time
740+
741+
logger = logging.getLogger(__name__)
742+
743+
def useful():
744+
# Just a representative event logged from the library
745+
logger.debug('Hello from webapplib!')
746+
# Just sleep for a bit so other threads get to run
747+
time.sleep(0.01)
748+
749+
We can simulate the multiple web applications by means of two simple classes,
750+
``Request`` and ``WebApp``. These simulate how real threaded web applications work -
751+
each request is handled by a thread:
752+
753+
.. code-block:: python
754+
755+
# main.py
756+
import argparse
757+
from contextvars import ContextVar
758+
import logging
759+
import os
760+
from random import choice
761+
import threading
762+
import webapplib
763+
764+
logger = logging.getLogger(__name__)
765+
root = logging.getLogger()
766+
root.setLevel(logging.DEBUG)
767+
768+
class Request:
769+
"""
770+
A simple dummy request class which just holds dummy HTTP request method,
771+
client IP address and client username
772+
"""
773+
def __init__(self, method, ip, user):
774+
self.method = method
775+
self.ip = ip
776+
self.user = user
777+
778+
# A dummy set of requests which will be used in the simulation - we'll just pick
779+
# from this list randomly. Note that all GET requests are from 192.168.2.XXX
780+
# addresses, whereas POST requests are from 192.16.3.XXX addresses. Three users
781+
# are represented in the sample requests.
782+
783+
REQUESTS = [
784+
Request('GET', '192.168.2.20', 'jim'),
785+
Request('POST', '192.168.3.20', 'fred'),
786+
Request('GET', '192.168.2.21', 'sheila'),
787+
Request('POST', '192.168.3.21', 'jim'),
788+
Request('GET', '192.168.2.22', 'fred'),
789+
Request('POST', '192.168.3.22', 'sheila'),
790+
]
791+
792+
# Note that the format string includes references to request context information
793+
# such as HTTP method, client IP and username
794+
795+
formatter = logging.Formatter('%(threadName)-11s %(appName)s %(name)-9s %(user)-6s %(ip)s %(method)-4s %(message)s')
796+
797+
# Create our context variables. These will be filled at the start of request
798+
# processing, and used in the logging that happens during that processing
799+
800+
ctx_request = ContextVar('request')
801+
ctx_appname = ContextVar('appname')
802+
803+
class InjectingFilter(logging.Filter):
804+
"""
805+
A filter which injects context-specific information into logs and ensures
806+
that only information for a specific webapp is included in its log
807+
"""
808+
def __init__(self, app):
809+
self.app = app
810+
811+
def filter(self, record):
812+
request = ctx_request.get()
813+
record.method = request.method
814+
record.ip = request.ip
815+
record.user = request.user
816+
record.appName = appName = ctx_appname.get()
817+
return appName == self.app.name
818+
819+
class WebApp:
820+
"""
821+
A dummy web application class which has its own handler and filter for a
822+
webapp-specific log.
823+
"""
824+
def __init__(self, name):
825+
self.name = name
826+
handler = logging.FileHandler(name + '.log', 'w')
827+
f = InjectingFilter(self)
828+
handler.setFormatter(formatter)
829+
handler.addFilter(f)
830+
root.addHandler(handler)
831+
self.num_requests = 0
832+
833+
def process_request(self, request):
834+
"""
835+
This is the dummy method for processing a request. It's called on a
836+
different thread for every request. We store the context information into
837+
the context vars before doing anything else.
838+
"""
839+
ctx_request.set(request)
840+
ctx_appname.set(self.name)
841+
self.num_requests += 1
842+
logger.debug('Request processing started')
843+
webapplib.useful()
844+
logger.debug('Request processing finished')
845+
846+
def main():
847+
fn = os.path.splitext(os.path.basename(__file__))[0]
848+
adhf = argparse.ArgumentDefaultsHelpFormatter
849+
ap = argparse.ArgumentParser(formatter_class=adhf, prog=fn,
850+
description='Simulate a couple of web '
851+
'applications handling some '
852+
'requests, showing how request '
853+
'context can be used to '
854+
'populate logs')
855+
aa = ap.add_argument
856+
aa('--count', '-c', default=100, help='How many requests to simulate')
857+
options = ap.parse_args()
858+
859+
# Create the dummy webapps and put them in a list which we can use to select
860+
# from randomly
861+
app1 = WebApp('app1')
862+
app2 = WebApp('app2')
863+
apps = [app1, app2]
864+
threads = []
865+
# Add a common handler which will capture all events
866+
handler = logging.FileHandler('app.log', 'w')
867+
handler.setFormatter(formatter)
868+
root.addHandler(handler)
869+
870+
# Generate calls to process requests
871+
for i in range(options.count):
872+
try:
873+
# Pick an app at random and a request for it to process
874+
app = choice(apps)
875+
request = choice(REQUESTS)
876+
# Process the request in its own thread
877+
t = threading.Thread(target=app.process_request, args=(request,))
878+
threads.append(t)
879+
t.start()
880+
except KeyboardInterrupt:
881+
break
882+
883+
# Wait for the threads to terminate
884+
for t in threads:
885+
t.join()
886+
887+
for app in apps:
888+
print('%s processed %s requests' % (app.name, app.num_requests))
889+
890+
if __name__ == '__main__':
891+
main()
892+
893+
If you run the above, you should find that roughly half the requests go
894+
into :file:`app1.log` and the rest into :file:`app2.log`, and the all the requests are
895+
logged to :file:`app.log`. Each webapp-specific log will contain only log entries for
896+
only that webapp, and the request information will be displayed consistently in the
897+
log (i.e. the information in each dummy request will always appear together in a log
898+
line). This is illustrated by the following shell output:
899+
900+
.. code-block:: shell
901+
902+
~/logging-contextual-webapp$ python main.py
903+
app1 processed 51 requests
904+
app2 processed 49 requests
905+
~/logging-contextual-webapp$ wc -l *.log
906+
153 app1.log
907+
147 app2.log
908+
300 app.log
909+
600 total
910+
~/logging-contextual-webapp$ head -3 app1.log
911+
Thread-3 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
912+
Thread-3 (process_request) app1 webapplib jim 192.168.3.21 POST Hello from webapplib!
913+
Thread-5 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
914+
~/logging-contextual-webapp$ head -3 app2.log
915+
Thread-1 (process_request) app2 __main__ sheila 192.168.2.21 GET Request processing started
916+
Thread-1 (process_request) app2 webapplib sheila 192.168.2.21 GET Hello from webapplib!
917+
Thread-2 (process_request) app2 __main__ jim 192.168.2.20 GET Request processing started
918+
~/logging-contextual-webapp$ head app.log
919+
Thread-1 (process_request) app2 __main__ sheila 192.168.2.21 GET Request processing started
920+
Thread-1 (process_request) app2 webapplib sheila 192.168.2.21 GET Hello from webapplib!
921+
Thread-2 (process_request) app2 __main__ jim 192.168.2.20 GET Request processing started
922+
Thread-3 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
923+
Thread-2 (process_request) app2 webapplib jim 192.168.2.20 GET Hello from webapplib!
924+
Thread-3 (process_request) app1 webapplib jim 192.168.3.21 POST Hello from webapplib!
925+
Thread-4 (process_request) app2 __main__ fred 192.168.2.22 GET Request processing started
926+
Thread-5 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
927+
Thread-4 (process_request) app2 webapplib fred 192.168.2.22 GET Hello from webapplib!
928+
Thread-6 (process_request) app1 __main__ jim 192.168.3.21 POST Request processing started
929+
~/logging-contextual-webapp$ grep app1 app1.log | wc -l
930+
153
931+
~/logging-contextual-webapp$ grep app2 app2.log | wc -l
932+
147
933+
~/logging-contextual-webapp$ grep app1 app.log | wc -l
934+
153
935+
~/logging-contextual-webapp$ grep app2 app.log | wc -l
936+
147
937+
938+
717939
Imparting contextual information in handlers
718940
--------------------------------------------
719941

Doc/library/ctypes.rst

+30-7
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,14 @@ Calling functions
148148
^^^^^^^^^^^^^^^^^
149149

150150
You can call these functions like any other Python callable. This example uses
151-
the ``time()`` function, which returns system time in seconds since the Unix
152-
epoch, and the ``GetModuleHandleA()`` function, which returns a win32 module
153-
handle.
151+
the ``rand()`` function, which takes no arguments and returns a pseudo-random integer::
154152

155-
This example calls both functions with a ``NULL`` pointer (``None`` should be used
156-
as the ``NULL`` pointer)::
153+
>>> print(libc.rand()) # doctest: +SKIP
154+
1804289383
155+
156+
On Windows, you can call the ``GetModuleHandleA()`` function, which returns a win32 module
157+
handle (passing ``None`` as single argument to call it with a ``NULL`` pointer)::
157158

158-
>>> print(libc.time(None)) # doctest: +SKIP
159-
1150640792
160159
>>> print(hex(windll.kernel32.GetModuleHandleA(None))) # doctest: +WINDOWS
161160
0x1d000000
162161
>>>
@@ -247,6 +246,8 @@ Fundamental data types
247246
| :class:`c_ssize_t` | :c:type:`ssize_t` or | int |
248247
| | :c:type:`Py_ssize_t` | |
249248
+----------------------+------------------------------------------+----------------------------+
249+
| :class:`c_time_t` | :c:type:`time_t` | int |
250+
+----------------------+------------------------------------------+----------------------------+
250251
| :class:`c_float` | :c:type:`float` | float |
251252
+----------------------+------------------------------------------+----------------------------+
252253
| :class:`c_double` | :c:type:`double` | float |
@@ -447,6 +448,21 @@ By default functions are assumed to return the C :c:type:`int` type. Other
447448
return types can be specified by setting the :attr:`restype` attribute of the
448449
function object.
449450

451+
The C prototype of ``time()`` is ``time_t time(time_t *)``. Because ``time_t``
452+
might be of a different type than the default return type ``int``, you should
453+
specify the ``restype``::
454+
455+
>>> libc.time.restype = c_time_t
456+
457+
The argument types can be specified using ``argtypes``::
458+
459+
>>> libc.time.argtypes = (POINTER(c_time_t),)
460+
461+
To call the function with a ``NULL`` pointer as first argument, use ``None``::
462+
463+
>>> print(libc.time(None)) # doctest: +SKIP
464+
1150640792
465+
450466
Here is a more advanced example, it uses the ``strchr`` function, which expects
451467
a string pointer and a char, and returns a pointer to a string::
452468

@@ -2275,6 +2291,13 @@ These are the fundamental ctypes data types:
22752291
.. versionadded:: 3.2
22762292

22772293

2294+
.. class:: c_time_t
2295+
2296+
Represents the C :c:type:`time_t` datatype.
2297+
2298+
.. versionadded:: 3.12
2299+
2300+
22782301
.. class:: c_ubyte
22792302

22802303
Represents the C :c:type:`unsigned char` datatype, it interprets the value as

Doc/library/idle.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ One may edit pasted code first.
594594
If one pastes more than one statement into Shell, the result will be a
595595
:exc:`SyntaxError` when multiple statements are compiled as if they were one.
596596

597-
Lines containing ``'RESTART'`` mean that the user execution process has been
597+
Lines containing ``RESTART`` mean that the user execution process has been
598598
re-started. This occurs when the user execution process has crashed,
599599
when one requests a restart on the Shell menu, or when one runs code
600600
in an editor window.
@@ -775,7 +775,9 @@ IDLE's standard stream replacements are not inherited by subprocesses
775775
created in the execution process, whether directly by user code or by
776776
modules such as multiprocessing. If such subprocess use ``input`` from
777777
sys.stdin or ``print`` or ``write`` to sys.stdout or sys.stderr,
778-
IDLE should be started in a command line window. The secondary subprocess
778+
IDLE should be started in a command line window. (On Windows,
779+
use ``python`` or ``py`` rather than ``pythonw`` or ``pyw``.)
780+
The secondary subprocess
779781
will then be attached to that window for input and output.
780782

781783
If ``sys`` is reset by user code, such as with ``importlib.reload(sys)``,

Doc/library/logging.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ is the module's name in the Python package namespace.
233233
2006-02-08 22:20:02,165 192.168.0.1 fbloggs Protocol problem: connection reset
234234
235235
The keys in the dictionary passed in *extra* should not clash with the keys used
236-
by the logging system. (See the :class:`Formatter` documentation for more
236+
by the logging system. (See the section on :ref:`logrecord-attributes` for more
237237
information on which keys are used by the logging system.)
238238

239239
If you choose to use these attributes in logged messages, you need to exercise

Doc/library/shlex.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ The :mod:`shlex` module defines the following functions:
3636
instance, passing ``None`` for *s* will read the string to split from
3737
standard input.
3838

39-
.. deprecated:: 3.9
40-
Passing ``None`` for *s* will raise an exception in future Python
41-
versions.
39+
.. versionchanged:: 3.12
40+
Passing ``None`` for *s* argument now raises an exception, rather than
41+
reading :data:`sys.stdin`.
4242

4343
.. function:: join(split_command)
4444

Doc/library/sqlite3.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,8 @@ Now we plug :class:`Row` in::
10691069
35.14
10701070

10711071

1072+
.. _sqlite3-blob-objects:
1073+
10721074
Blob Objects
10731075
------------
10741076

@@ -1211,8 +1213,6 @@ The exception hierarchy is defined by the DB-API 2.0 (:pep:`249`).
12111213
``NotSupportedError`` is a subclass of :exc:`DatabaseError`.
12121214

12131215

1214-
.. _sqlite3-blob-objects:
1215-
12161216
.. _sqlite3-types:
12171217

12181218
SQLite and Python types

Doc/whatsnew/3.12.rst

+11
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ Removed
293293
a C implementation of :func:`~hashlib.pbkdf2_hmac()` which is faster.
294294
(Contributed by Victor Stinner in :gh:`94199`.)
295295

296+
* :mod:`xml.etree`: Remove the ``ElementTree.Element.copy()`` method of the
297+
pure Python implementation, deprecated in Python 3.10, use the
298+
:func:`copy.copy` function instead. The C implementation of :mod:`xml.etree`
299+
has no ``copy()`` method, only a ``__copy__()`` method.
300+
(Contributed by Victor Stinner in :gh:`94383`.)
301+
296302

297303
Porting to Python 3.12
298304
======================
@@ -324,6 +330,11 @@ Changes in the Python API
324330
to :term:`filesystem encoding and error handler`.
325331
Argument files should be encoded in UTF-8 instead of ANSI Codepage on Windows.
326332

333+
* :func:`shlex.split`: Passing ``None`` for *s* argument now raises an
334+
exception, rather than reading :data:`sys.stdin`. The feature was deprecated
335+
in Python 3.9.
336+
(Contributed by Victor Stinner in :gh:`94352`.)
337+
327338

328339
Build Changes
329340
=============

0 commit comments

Comments
 (0)