X Tutup
The Wayback Machine - https://web.archive.org/web/20231018224646/https://github.com/python/cpython/issues/38807
Skip to content
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

fix one or two bugs in trace.py #38807

Open
zooko mannequin opened this issue Jul 7, 2003 · 16 comments
Open

fix one or two bugs in trace.py #38807

zooko mannequin opened this issue Jul 7, 2003 · 16 comments
Labels
3.12 bugs and security fixes easy stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@zooko
Copy link
Mannequin

zooko mannequin commented Jul 7, 2003

BPO 766910
Nosy @abalkin, @devdanzin
Files
  • makedir.patch
  • trace-dir.patch.txt
  • trace_target.py
  • traced_module.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2003-07-07.01:46:30.000>
    labels = ['type-bug', 'library', '3.9', '3.10', '3.11']
    title = 'fix one or two bugs in trace.py'
    updated_at = <Date 2021-12-10.20:38:21.739>
    user = 'https://bugs.python.org/zooko'

    bugs.python.org fields:

    activity = <Date 2021-12-10.20:38:21.739>
    actor = 'ajaksu2'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2003-07-07.01:46:30.000>
    creator = 'zooko'
    dependencies = []
    files = ['5452', '8480', '19933', '19934']
    hgrepos = []
    issue_num = 766910
    keywords = ['patch']
    message_count = 14.0
    messages = ['44232', '55404', '56196', '56197', '58923', '65432', '114249', '123005', '123319', '126113', '126154', '126155', '190040', '190088']
    nosy_count = 5.0
    nosy_names = ['zooko', 'belopolsky', 'ajaksu2', 'ijmorlan', 'eli.bendersky']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = None
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue766910'
    versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']

    Linked PRs

    @zooko
    Copy link
    Mannequin Author

    zooko mannequin commented Jul 7, 2003

    There is one bug and one maybe-bug in creation of the
    coverage file directory in trace.py.

    The maybe-bug is that it doesn't attempt to create the
    directory if it got the directory name from the name of
    the .py file (instead of getting the directory name
    from the --coverdir command line option).

    Normally the directory will already exist, but if you
    are writing out coverage files from a stored report
    (or, I suppose, if someone deleted the directory after
    the modules were loaded but before you wrote out the
    report), then it won't. This patch makes it so that it
    always attempts to create the directory before
    attempting to write files into it. The patch also adds
    a statement to that effect to the "--help" usage info.

    The other bug is that it attempts to create a directory
    with:

                    if not os.path.exists(dir):
                        os.makedirs(dir)

    , which is a race condition that will very rarely raise
    a spurious exception ("File exists:") if two threads or
    processes execute it at the same time.

    The fix provided in this patch is to copy from my
    pyutil project [1] a utility function that works around
    this race condition and invoke that function. On
    request I'll provide a slimmed-down version of that
    function, since we don't use all of its options in
    trace.py.

    This patch hasn't been tested at ALL. In fact, I
    haven't tested trace.py in general since before it
    moved from Tools/scripts to Lib/. As this patch ought
    to go into Python 2.3, it ought to be tested, and I
    promise to do so soon.

    [1] http://sf.net/projects/pyutil

    @zooko zooko mannequin added stdlib Python modules in the Lib dir labels Jul 7, 2003
    @smontanaro
    Copy link
    Contributor

    Zooko, is this patch still necessary?

    @zooko
    Copy link
    Mannequin Author

    zooko mannequin commented Sep 29, 2007

    Hi! Sorry it took me so long to look at this. I just checked the
    source in current trunk, and the relevant code is the same so this
    patch is still useful. (See the initial post for details.)

    Here is an updated version of the patch which simply removes some
    dead code and updates a URL:

    regards,

    Zooko

    diff -rN -u old-up/setuptools-0.6c7/ez_setup.py new-up/ 
    setuptools-0.6c7/ez_setup.py
    --- old-up/setuptools-0.6c7/ez_setup.py	2007-09-28 16:41:24.000000000  
    -0600
    +++ new-up/setuptools-0.6c7/ez_setup.py	2007-09-28 16:41:25.000000000  
    -0600
    @@ -1,4 +1,4 @@
    -#!python
    +#!/usr/bin/env python
    """Bootstrap setuptools installation
    If you want to use setuptools in your package's setup.py, just  
    include this
    @@ -13,7 +13,7 @@
    This file can also be run as a script to install or upgrade setuptools.
    """
    -import sys
    +import os, re, subprocess, sys
    DEFAULT_VERSION = "0.6c7"
    DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/"  
    % sys.version[:3]
    @@ -44,8 +44,6 @@
          'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
    }
    -import sys, os
    -
    def _validate_md5(egg_name, data):
          if egg_name in md5_data:
              from md5 import md5
    @@ -58,6 +56,42 @@
                  sys.exit(2)
          return data
    +# The following code to parse versions is copied from  
    pkg_resources.py so that
    +# we can parse versions without importing that module.
    +component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
    +replace = {'pre':'c',  
    'preview':'c','-':'final-','rc':'c','dev':'@'}.get
    +
    +def _parse_version_parts(s):
    +    for part in component_re.split(s):
    +        part = replace(part,part)
    +        if not part or part=='.':
    +            continue
    +        if part[:1] in '0123456789':
    +            yield part.zfill(8)    # pad for numeric comparison
    +        else:
    +            yield '*'+part
    +
    +    yield '*final'  # ensure that alpha/beta/candidate are before final
    +
    +def parse_version(s):
    +    parts = []
    +    for part in _parse_version_parts(s.lower()):
    +        if part.startswith('*'):
    +            if part<'*final':   # remove '-' before a prerelease tag
    +                while parts and parts[-1]=='*final-': parts.pop()
    +            # remove trailing zeros from each series of numeric parts
    +            while parts and parts[-1]=='00000000':
    +                parts.pop()
    +        parts.append(part)
    +    return tuple(parts)
    +
    +def setuptools_is_new_enough(required_version):
    +    """Return True if setuptools is already installed and has a version
    +    number >= required_version."""
    +    sub = subprocess.Popen([sys.executable, "-c", "import  
    setuptools;print setuptools.__version__"], stdout=subprocess.PIPE)
    +    verstr = sub.stdout.read().strip()
    +    ver = parse_version(verstr)
    +    return ver and ver >= parse_version(required_version)
    def use_setuptools(
          version=DEFAULT_VERSION, download_base=DEFAULT_URL,  
    to_dir=os.curdir,
    @@ -74,32 +108,11 @@
          this routine will print a message to ``sys.stderr`` and raise  
    SystemExit in
          an attempt to abort the calling script.
          """
    -    try:
    -        import setuptools
    -        if setuptools.__version__ == '0.0.1':
    -            print >>sys.stderr, (
    -            "You have an obsolete version of setuptools installed.   
    Please\n"
    -            "remove it from your system entirely before rerunning  
    this script."
    -            )
    -            sys.exit(2)
    -    except ImportError:
    +    if not setuptools_is_new_enough(version):
              egg = download_setuptools(version, download_base, to_dir,  
    download_delay)
              sys.path.insert(0, egg)
              import setuptools; setuptools.bootstrap_install_from = egg
    -    import pkg_resources
    -    try:
    -        pkg_resources.require("setuptools>="+version)
    -
    -    except pkg_resources.VersionConflict, e:
    -        # XXX could we install in a subprocess here?
    -        print >>sys.stderr, (
    -            "The required version of setuptools (>=%s) is not  
    available, and\n"
    -            "can't be installed while this script is running. Please  
    install\n"
    -            " a more recent version first.\n\n(Currently using %r)"
    -        ) % (version, e.args[0])
    -        sys.exit(2)
    -
    def download_setuptools(
          version=DEFAULT_VERSION, download_base=DEFAULT_URL,  
    to_dir=os.curdir,
          delay = 15
    @@ -150,9 +163,14 @@
    def main(argv, version=DEFAULT_VERSION):
          """Install or upgrade setuptools and EasyInstall"""
    -    try:
    -        import setuptools
    -    except ImportError:
    +    if setuptools_is_new_enough(version):
    +        if argv:
    +            from setuptools.command.easy_install import main
    +            main(argv)
    +        else:
    +            print "Setuptools version",version,"or greater has been  
    installed."
    +            print '(Run "ez_setup.py -U setuptools" to reinstall or  
    upgrade.)'
    +    else:
              egg = None
              try:
                  egg = download_setuptools(version, delay=0)
    @@ -162,31 +180,6 @@
              finally:
                  if egg and os.path.exists(egg):
                      os.unlink(egg)
    -    else:
    -        if setuptools.__version__ == '0.0.1':
    -            # tell the user to uninstall obsolete version
    -            use_setuptools(version)
    -
    -    req = "setuptools>="+version
    -    import pkg_resources
    -    try:
    -        pkg_resources.require(req)
    -    except pkg_resources.VersionConflict:
    -        try:
    -            from setuptools.command.easy_install import main
    -        except ImportError:
    -            from easy_install import main
    -        main(list(argv)+[download_setuptools(delay=0)])
    -        sys.exit(0) # try to force an exit
    -    else:
    -        if argv:
    -            from setuptools.command.easy_install import main
    -            main(argv)
    -        else:
    -            print "Setuptools version",version,"or greater has been  
    installed."
    -            print '(Run "ez_setup.py -U setuptools" to reinstall or  
    upgrade.)'
    -
    -
    def update_md5(filenames):
          """Update our built-in md5 registry"""

    @zooko
    Copy link
    Mannequin Author

    zooko mannequin commented Sep 29, 2007

    Here is an updated version of the patch which simply removes some
    dead code and updates a URL:

    regards,

    Zooko

    diff -rN -u old-up/setuptools-0.6c7/ez_setup.py new-up/
    setuptools-0.6c7/ez_setup.py
    --- old-up/setuptools-0.6c7/ez_setup.py 2007-09-28
    16:41:24.000000000 -0600
    +++ new-up/setuptools-0.6c7/ez_setup.py 2007-09-28
    16:41:25.000000000 -0600

    Oops, the in-lined patch contents were a different patch entirely,
    but the attached patch file was correct.

    Just for completeness, here is the correct in-lined patch contents:

    Index: Lib/trace.py
    ===================================================================

    --- Lib/trace.py	(revision 58282)
    +++ Lib/trace.py	(working copy)
    @@ -85,7 +85,12 @@
    -r, --report          Generate a report from a counts file; do not  
    execute
                            any code.  `--file' must specify the results  
    file to
                            read, which must have been created in a  
    previous run
    -                      with `--count --file=FILE'.
    +                      with `--count --file=FILE'.  If --coverdir is not
    +                      specified, the .cover files will be written  
    into the
    +                      directory that the modules were in when the  
    report was
    +                      generated.  Whether or not --coverdir is  
    specified,
    +                      --report will always create the cover file  
    directory if
    +                      necessary.
    Modifiers:
    -f, --file=<file>     File to accumulate counts over several runs.
    @@ -197,6 +202,33 @@
          filename, ext = os.path.splitext(base)
          return filename
    +# The following function is copied from the fileutil module from the  
    pyutil
    +# project:
    +# http://pypi.python.org/pypi/pyutil
    +# We use this function instead of os.makedirs() so that we don't get a
    +# spurious exception when someone else creates the directory at the  
    same
    +# moment we do.  (For example, another thread or process that is  
    also running
    +# trace.)
    +def make_dirs(dirname, mode=0777):
    +    """
    +    A threadsafe and idempotent version of os.makedirs().  If the  
    dir already
    +    exists, do nothing and return without raising an exception.  If  
    this call
    +    creates the dir, return without raising an exception.  If there  
    is an
    +    error that prevents creation or if the directory gets deleted after
    +    make_dirs() creates it and before make_dirs() checks that it  
    exists, raise
    +    an exception.
    +    """
    +    tx = None
    +    try:
    +        os.makedirs(dirname, mode)
    +    except OSError, x:
    +        tx = x
    +
    +    if not os.path.isdir(dirname):
    +        if tx:
    +            raise tx
    +        raise exceptions.IOError, "unknown error prevented creation  
    of directory, or deleted the directory immediately after creation: % 
    s" % dirname # careful not to construct an IOError with a 2-tuple, as  
    that has a special meaning...
    +
    class CoverageResults:
          def __init__(self, counts=None, calledfuncs=None, infile=None,
                       callers=None, outfile=None):
    @@ -290,15 +322,15 @@
                  if filename.endswith((".pyc", ".pyo")):
                      filename = filename[:-1]
    -            if coverdir is None:
    +            if coverdir is not None:
    +                dir = coverdir
    +                modulename = fullmodname(filename)
    +            else:
                      dir = os.path.dirname(os.path.abspath(filename))
                      modulename = modname(filename)
    -            else:
    -                dir = coverdir
    -                if not os.path.exists(dir):
    -                    os.makedirs(dir)
    -                modulename = fullmodname(filename)
    +            make_dirs(dir)
    +
                  # If desired, get a list of the line numbers which  
    represent
                  # executable content (returned as a dict for better  
    lookup speed)
                  if show_missing:

    @ijmorlan
    Copy link
    Mannequin

    ijmorlan mannequin commented Dec 20, 2007

    I would suggest that the need to create directories that may already
    exist (really "ensure existence of" directories) is not exclusive to
    trace.py. I am suggesting this be added as an option to os.mkdir and
    os.makedirs. See bpo-1675.

    @smontanaro
    Copy link
    Contributor

    unassigning

    @BreamoreBoy
    Copy link
    Mannequin

    BreamoreBoy mannequin commented Aug 18, 2010

    Is there any interest in this issue?

    @BreamoreBoy BreamoreBoy mannequin added type-bug An unexpected behavior, bug, or error labels Aug 18, 2010
    @abalkin
    Copy link
    Member

    abalkin commented Dec 1, 2010

    Eli,

    Would you like to review this patch?

    @elibendersky
    Copy link
    Mannequin

    elibendersky mannequin commented Dec 4, 2010

    Alexander,

    I reviewed the patch and ported the changes to the newest sources (since the fix to bpo-9299, os.makedirs can be naturally used with its new flag to fix the bug Zooko refers to).

    However, while experimenting, I think I ran into much larger problems. Either that or I've forgotten how to use the module :-) Attaching two files (one imports the other) on which I try to run the following:

    python -m trace -c trace_target.py

    > OK: I get trace_target.cover & traced_module.cover created

    However, now running:

    python -m trace -r --file=trace_target.cover

    > ...
    pickle.load(open(self.infile, 'rb'))
    _pickle.UnpicklingError: invalid load key, ' '.

    Also, trying to provide --file to -c:

    python -m trace -c trace_target.py --file=xyz.cover

    > xyz.cover is ignored and the same two .cover files are created.

    Can you take a look at this?

    @abalkin
    Copy link
    Member

    abalkin commented Jan 12, 2011

    On Sat, Dec 4, 2010 at 3:34 AM, Eli Bendersky <report@bugs.python.org> wrote:

    ..
    However, while experimenting, I think I ran into much larger problems. Either that or I've forgotten
    how to use the module :-)

    I am afraid it is the latter. :-) The file specified in --file option
    should be a pickle, not a coverage file from the previous run.

    $ ./python.exe -m trace -s -f counts.pickle -c trace_target.py
    K is 380
    Skipping counts file 'counts.pickle': [Errno 2] No such file or
    directory: 'counts.pickle'
    lines   cov%   module   (path)
        1   100%   trace   (/Users/sasha/Work/python-svn/py3k-commit/Lib/trace.py)
        9   100%   trace_target   (trace_target.py)
        6   100%   traced_module   (traced_module.py)
    
    $ ./python.exe -m pickletools counts.pickle
        0: (    MARK
        1: }        EMPTY_DICT
        2: q        BINPUT     0
        4: (        MARK
        5: (            MARK
        6: X                BINUNICODE 'trace_target.py'
    ...

    However, there is a problem here, I think:

    $ ./python.exe -m trace -s -f counts.pickle -c trace_target.py
    K is 380
    lines   cov%   module   (path)
        1   100%   trace   (/Users/sasha/Work/python-svn/py3k-commit/Lib/trace.py)
        9   100%   trace_target   (trace_target.py)
        6   100%   traced_module   (traced_module.py)
    $ ./python.exe -m trace -s -f counts.pickle -c trace_target.py
    K is 380
    lines   cov%   module   (path)
        1   100%   trace   (/Users/sasha/Work/python-svn/py3k-commit/Lib/trace.py)
        9   100%   trace_target   (trace_target.py)
        6   100%   traced_module   (traced_module.py)

    The counts should grow in repeated runs.

    @abalkin
    Copy link
    Member

    abalkin commented Jan 13, 2011

    On Wed, Jan 12, 2011 at 12:17 PM, Alexander Belopolsky
    <report@bugs.python.org> wrote:
    ..

    The counts should grow in repeated runs.

    I did not pay attention: the numbers in summary are numbers of lines,
    not execution counts. The execution counts in .cover file grow as
    expected.

    @abalkin
    Copy link
    Member

    abalkin commented Jan 13, 2011

    On Sat, Dec 4, 2010 at 3:34 AM, Eli Bendersky <report@bugs.python.org> wrote:
    ..

    I reviewed the patch and ported the changes to the newest sources

    Eli,

    I don't think you ever posted an updated patch. Do you still have it?

    This may be a good starting issue for you as a committer.

    @BreamoreBoy
    Copy link
    Mannequin

    BreamoreBoy mannequin commented May 26, 2013

    Just a gentle bump.

    @elibendersky
    Copy link
    Mannequin

    elibendersky mannequin commented May 26, 2013

    Ah, no time, no time... :-/

    I may get back to this in the future. Bumping to more relevant versions for now.

    @abalkin abalkin added the 3.7 (EOL) end of life label Sep 10, 2016
    @abalkin abalkin removed their assignment Sep 10, 2016
    @abalkin abalkin added the 3.7 (EOL) end of life label Sep 10, 2016
    @abalkin abalkin removed their assignment Sep 10, 2016
    @devdanzin devdanzin mannequin added 3.9 only security fixes 3.10 only security fixes labels Dec 10, 2021
    @devdanzin devdanzin mannequin added 3.11 bug and security fixes 3.9 only security fixes 3.10 only security fixes and removed 3.7 (EOL) end of life labels Dec 10, 2021
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 9, 2022
    @iritkatriel iritkatriel added easy 3.12 bugs and security fixes and removed 3.9 only security fixes 3.11 bug and security fixes 3.10 only security fixes labels Mar 2, 2023
    @hellozee
    Copy link

    hellozee commented Jul 9, 2023

    Hoi, was poking around the codebase looking to learn something, if I follow the conversation correctly, is this the patch which is being talked about for this issue?

    diff --git a/Lib/trace.py b/Lib/trace.py
    index fb9a423ea0..0e20d3ab73 100755
    --- a/Lib/trace.py
    +++ b/Lib/trace.py
    @@ -258,9 +258,8 @@ def write_results(self, show_missing=True, summary=False, coverdir=None):
                     modulename = _modname(filename)
                 else:
                     dir = coverdir
    -                if not os.path.exists(dir):
    -                    os.makedirs(dir)
                     modulename = _fullmodname(filename)
    +            os.makedirs(dir, exist_ok=True)
    
                 # If desired, get a list of the line numbers which represent
                 # executable content (returned as a dict for better lookup speed)

    @buermarc
    Copy link
    Contributor

    TLDR at the bottom, some history at the top.

    Original Issues (2003) #38807:

    • Bug (as I understood it): Using os.path.exists to check if a file exists and if not creating it with os.makedirs could lead to a failure if it was created in between.

    • Maybe Bug:

      The maybe-bug is that it doesn't attempt to create the
      directory if it got the directory name from the name of
      the .py file (instead of getting the directory name
      from the --coverdir command line option).

      I am unsure if the maybe bug is really a bug, because even if we use a
      pickle file it seems to that we still would try to open the source file,
      therefore assuming it exists.
      As it might be opened two times, but definetly here:

      with open(filename, 'rb') as fp:

      So maybe the assumption that the source file must exist is okay, below a longer snippet from Lib/trace.py, with # <---- comments where we seem to open the file.

      for filename, count in per_file.items():
      if self.is_ignored_filename(filename):
          continue
      
      if filename.endswith(".pyc"):
          filename = filename[:-1]
      
      if coverdir is None:
          dir = os.path.dirname(os.path.abspath(filename))
          modulename = _modname(filename)
      else:
          dir = coverdir
          if not os.path.exists(dir):
              os.makedirs(dir)
          modulename = _fullmodname(filename)
      
      # If desired, get a list of the line numbers which represent
      # executable content (returned as a dict for better lookup speed)
      if show_missing:
          lnotab = _find_executable_linenos(filename)
      else:
          lnotab = {}
      source = linecache.getlines(filename)  # <---- Maybe opening the file here
      coverpath = os.path.join(dir, modulename + ".cover")
      with open(filename, 'rb') as fp:   # <---- Opening the file here
          encoding, _ = tokenize.detect_encoding(fp.readline)
      n_hits, n_lines = self.write_results_file(coverpath, source,
                                                lnotab, count, encoding)

    Race Condition in os.makedirs (2007) #46016:

    Superseeder (2010) #53545:

    Vulnerability fix (2014) #65281:

    benjaminp:

    I've now removed the offending behavior. exist_ok is still racy because it
    uses path.isdir() in the exceptional case, but fixing that can be an
    enhancement elsewhere.

    So yeah there is still a problem lurking here, but I am not quite sure if it is
    relevant enough to be addressed in the case of this issue.

    Issue #25583: Avoid incorrect errors raised by os.makedirs(..., exist_ok=True):

    • Changed some error handling but os.path.isdir is still used
    tree 39ae79f83900575d6dba600ebec8deb3917508eb
    parent 41f69f4cc7c977bd202545b8ada01b80a278d0e2
    author Martin Panter <vadmium+py@gmail.com> Thu Nov 19 04:48:44 2015 +0000
    committer Martin Panter <vadmium+py@gmail.com> Thu Nov 19 04:48:44 2015 +0000
    
    Issue #25583: Avoid incorrect errors raised by os.makedirs(exist_ok=True)
    
    
    diff --git a/Lib/os.py b/Lib/os.py
    index a8f6a0b8db..27b241ae97 100644
    --- a/Lib/os.py
    +++ b/Lib/os.py
    @@ -226,7 +226,7 @@ def makedirs(name, mode=0o777, exist_ok=False):
             try:
                 makedirs(head, mode, exist_ok)
             except FileExistsError:
    -            # be happy if someone already created the path
    +            # Defeats race condition when another thread created the path
                 pass
             cdir = curdir
             if isinstance(tail, bytes):
    @@ -235,8 +235,10 @@ def makedirs(name, mode=0o777, exist_ok=False):
                 return
         try:
             mkdir(name, mode)
    -    except OSError as e:
    -        if not exist_ok or e.errno != errno.EEXIST or not path.isdir(name):
    +    except OSError:
    +        # Cannot rely on checking for EEXIST, since the operating system
    +        # could give priority to other errors like EACCES or EROFS
    +        if not exist_ok or not path.isdir(name):
                 raise

    TLDR:

    There is a possible race condition where after the os.path.exists an
    os.makedirs call might fail if the directory is created in between. Using
    os.makedirs(..., exist_ok=True) would somewhat solve this. However, there
    might still be a problem within os.makedirs(..., exits_ok=True). At first glance it did not
    seem to be that relevant to this issue.

    Moving the os.makedirs out of the if case might not be necessary, as it seems
    to me that if the source directory is deleted we will fail as soon as we try to
    retrieve the source via the linecache module given the source is not loaded.
    Or as soon as we open the filename in order to retrieve the encoding.

    @hellozee Does this somewhat match how you followed the issue?

    buermarc added a commit to buermarc/cpython that referenced this issue Sep 27, 2023
    Instead of checking if a directory does not exist and thereafter
    creating it, directly call `os.makedirs` with the `exist_ok` kwarg.
    buermarc added a commit to buermarc/cpython that referenced this issue Sep 30, 2023
    Instead of checking if a directory does not exist and thereafter
    creating it, directly call `os.makedirs` with the `exist_ok` kwarg.
    buermarc added a commit to buermarc/cpython that referenced this issue Sep 30, 2023
    Instead of checking if a directory does not exist and thereafter
    creating it, directly call `os.makedirs` with the `exist_ok` kwarg.
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.12 bugs and security fixes easy stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    5 participants
    X Tutup