chmod in Python

os.chmod(path, 0o755)

os.chmod(path, 0o755) sets a file’s mode to rwxr-xr-x, the same as chmod 755 in a shell. The 0o prefix makes 755 an octal literal, and the mode has to be octal because each digit packs three bits: read 4, write 2, execute 1.

import os, stat

os.chmod('deploy.sh', 0o755)
print(oct(os.stat('deploy.sh').st_mode & 0o777))    # 0o755
print(stat.filemode(os.stat('deploy.sh').st_mode))  # -rwxr-xr-x

The tool below reads 755 the same way: rwxr-xr-x, owner rwx, group and others r-x.

Without 0o, 755 is decimal

os.chmod('deploy.sh', 755)
m = os.stat('deploy.sh').st_mode
print(oct(stat.S_IMODE(m)), stat.filemode(m))   # 0o1363 --wxrw--wt

Decimal 755 is octal 1363: the owner can’t read the file, the group can write it but not run it, others can write and run it but not read it, and the sticky bit is on. Python raises nothing and warns about nothing; this ran on Linux and macOS alike. Type 1363 into the tool to see the same -wxrw--wt. Python 2’s 0755 spelling no longer parses: Python 3 stops with SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers.

A mode stored as text needs int(text, 8); os.chmod(path, '755') raises TypeError: 'str' object cannot be interpreted as an integer.

The stat constants and changing one bit

The stat module names each bit, which reads better in code review than a bare number:

p = 'deploy.sh'
os.chmod(p, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)   # 0o755
os.chmod(p, os.stat(p).st_mode | stat.S_IXUSR)   # like chmod u+x: 0o644 becomes 0o744

os.chmod always sets the whole mode, so to add or remove one bit, start from the current st_mode, as the second line does.

Reading a mode back

st_mode holds the file type as well: for that 0o744 file it’s 33252, or 0o100744. & 0o777 keeps the nine permission bits, and stat.S_IMODE() keeps setuid, setgid and sticky too. For the bare digits, use f'{os.stat(p).st_mode & 0o777:o}', which gives 744.

pathlib, new files and Windows

  • Path('deploy.sh').chmod(0o755) does the same as os.chmod.
  • A mode given while creating is filtered by the umask: os.makedirs('out/logs', mode=0o777) gives 0o755 under umask 022. For a secret, create it private rather than fixing it afterwards: os.open('secret.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) makes it -rw-------.
  • On Windows, the Python docs say os.chmod can only set the read-only flag (stat.S_IREAD or stat.S_IWRITE); all other bits are ignored.

Try it in the chmod Calculator

755 rwxr-xr-x

WhoReadWriteExecute
Owner
Group
Others
Special bits
Command
chmod 755 file

Runs in your browser. Nothing you enter is sent anywhere.