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 asos.chmod.- A mode given while creating is filtered by the umask:
os.makedirs('out/logs', mode=0o777)gives0o755under umask022. 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.chmodcan only set the read-only flag (stat.S_IREADorstat.S_IWRITE); all other bits are ignored.