30

On Python 2.7 os.makedirs() is missing exist_ok. This is available in Python 3 only.

I know that this is the a working work around:

try:
    os.makedirs(settings.STATIC_ROOT)
except OSError as e:
    if e.errno != errno.EEXIST:
        raise

I could create a custom my_make_dirs() method and use this, instead of os.makedirs(), but this is not nice.

What is the most pythonic work around, if you forced to support Python 2.7?

AFAIK python-future or six won't help here.

crizCraig
  • 8,487
  • 6
  • 54
  • 53
guettli
  • 25,042
  • 81
  • 346
  • 663

4 Answers4

25

One way around it is using pathlib. It has a backport for Python 2 and its mkdir() function supports exist_ok.

try:
  from pathlib import Path
except ImportError:
  from pathlib2 import Path  # python 2 backport

Path(settings.STATIC_ROOT).mkdir(exist_ok=True)

As the comment suggests, use parents=True for makedirs().

Path(settings.STATIC_ROOT).mkdir(exist_ok=True, parents=True)
kichik
  • 33,220
  • 7
  • 94
  • 114
19

You could call makedirs() after checking that the path does not exist:

import os

if not os.path.exists(path):
    os.makedirs(path)
Pat Myron
  • 4,437
  • 2
  • 20
  • 39
  • 7
    This is worse than OP's version because of a race condition – Alexei Averchenko May 24 '19 at 13:05
  • 4
    @AlexeiAverchenko the OP did not ask for a thread-safe solution. The potential race condition doesn't make this solution bad nor worse... It just means this solution is fit for scenarios where there's no expectation for concurrent executions of it. Which, for many python processes (e.g. machine learning, devops, etc.), is perfectly good. – yair Dec 21 '20 at 08:50
0

What you are doing is probably fine, but if you want to detect and adapt, you can monkeypatch at runtime. I wouldn't say this is the best idea, it can result in some odd eventualities, but depending on your situation maybe it's fine. At a minimum, put some documentation in and around this code so the next guy or gal knows what's happening.

Here's an example of this - you can run this script with either "true" or something else as an argument and see the difference.

Detect the version of python by using sys.version_info: https://docs.python.org/2/library/sys.html#sys.version_info https://docs.python.org/3/library/sys.html#sys.version_info

import sys
import os


def blah(a):
    return "lskdflsdkfj"


if sys.argv[1] == "true":
    os.listdir = blah

print(os.listdir('.'))
jimf
  • 4,527
  • 1
  • 16
  • 21
0

The accepted answer incomplete as os.makedirs() creates subfolders recursively and Path.mkdir(), like os.mkdir(), can only create new directory in an existing place. Another approach is to leverage exceptions thrown to distinguish and tolerate behaviors between Python2/3.

import errno
import os
import os.path

def makedirs(folder, *args, **kwargs):
  try:
    return os.makedirs(folder, exist_ok=True, *args, **kwargs)
  except TypeError: 
    # Unexpected arguments encountered 
    pass

  try:
    # Should work is TypeError was caused by exist_ok, eg., Py2
    return os.makedirs(folder, *args, **kwargs)
  except OSError as e:
    if e.errno != errno.EEXIST:
      raise

    if os.path.isfile(folder):
      # folder is a file, raise OSError just like os.makedirs() in Py3
      raise

Also see: Python "FileExists" error when making directory

clifflu
  • 71
  • 1
  • 6