$ ruff --version
ruff 0.15.12
$ cat <<EOF > ./original.py
import os
from tempfile import NamedTemporaryFile
file = NamedTemporaryFile()
class TestName:
def __init__(self, name: str):
self.name = name
os.chmod(name, 0o644) # Case 1: Fix applied, correct results
def test_name(self):
os.chmod(self.name, 0o644) # Case 2: Fix applied, correct results
TestName(file.name).test_name()
class TestFd:
def __init__(self, fd: int):
self.fd = fd
os.chmod(fd, 0o644) # Case 3: Fix not applied, correct results
def test_fd(self):
os.chmod(self.fd, 0o644) # Case 4: Fix applied, incorrect results!
TestFd(file.fileno()).test_fd()
print("Done")
EOF
$ python3 ./original.py
Done
$ ruff --isolated --config='lint.select=["PTH101"]' check --preview --no-fix original.py
error[PTH101][*]: `os.chmod()` should be replaced by `Path.chmod()`
--> original.py:10:9
|
8 | def __init__(self, name: str):
9 | self.name = name
10 | os.chmod(name, 0o644) # Case 1: Fix applied, correct results
| ^^^^^^^^
11 |
12 | def test_name(self):
|
help: Replace with `Path(...).chmod(...)`
1 | import os
2 | from tempfile import NamedTemporaryFile
3 + import pathlib
4 |
5 | file = NamedTemporaryFile()
6 |
--------------------------------------------------------------------------------
8 | class TestName:
9 | def __init__(self, name: str):
10 | self.name = name
- os.chmod(name, 0o644) # Case 1: Fix applied, correct results
11 + pathlib.Path(name).chmod(0o644) # Case 1: Fix applied, correct results
12 |
13 | def test_name(self):
14 | os.chmod(self.name, 0o644) # Case 2: Fix applied, correct results
error[PTH101][*]: `os.chmod()` should be replaced by `Path.chmod()`
--> original.py:13:9
|
12 | def test_name(self):
13 | os.chmod(self.name, 0o644) # Case 2: Fix applied, correct results
| ^^^^^^^^
|
help: Replace with `Path(...).chmod(...)`
1 | import os
2 | from tempfile import NamedTemporaryFile
3 + import pathlib
4 |
5 | file = NamedTemporaryFile()
6 |
--------------------------------------------------------------------------------
11 | os.chmod(name, 0o644) # Case 1: Fix applied, correct results
12 |
13 | def test_name(self):
- os.chmod(self.name, 0o644) # Case 2: Fix applied, correct results
14 + pathlib.Path(self.name).chmod(0o644) # Case 2: Fix applied, correct results
15 |
16 |
17 | TestName(file.name).test_name()
error[PTH101][*]: `os.chmod()` should be replaced by `Path.chmod()`
--> original.py:25:9
|
24 | def test_fd(self):
25 | os.chmod(self.fd, 0o644) # Case 4: Fix applied, incorrect results!
| ^^^^^^^^
|
help: Replace with `Path(...).chmod(...)`
1 | import os
2 | from tempfile import NamedTemporaryFile
3 + import pathlib
4 |
5 | file = NamedTemporaryFile()
6 |
--------------------------------------------------------------------------------
23 | os.chmod(fd, 0o644) # Case 3: Fix not applied, correct results
24 |
25 | def test_fd(self):
- os.chmod(self.fd, 0o644) # Case 4: Fix applied, incorrect results!
26 + pathlib.Path(self.fd).chmod(0o644) # Case 4: Fix applied, incorrect results!
27 |
28 |
29 | TestFd(file.fileno()).test_fd()
Found 3 errors.
[*] 3 fixable with the `--fix` option.
$ cp ./original.py ./fixed.py
$ ruff --isolated --config='lint.select=["PTH101"]' check --preview --fix fixed.py
Found 3 errors (3 fixed, 0 remaining).
$ python3 ./fixed.py
Traceback (most recent call last):
File "./fixed.py", line 29, in <module>
TestFd(file.fileno()).test_fd()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "./fixed.py", line 26, in test_fd
pathlib.Path(self.fd).chmod(0o644) # Case 4: Fix applied, incorrect results!
~~~~~~~~~~~~^^^^^^^^^
File "/usr/lib64/python3.14/pathlib/__init__.py", line 150, in __init__
raise TypeError(
...<2 lines>...
f"not {type(path).__name__!r}")
TypeError: argument should be a str or an os.PathLike object where __fspath__ returns a str, not 'int'
$ cat fixed.py
# Setup code
import os
from tempfile import NamedTemporaryFile
import pathlib
file = NamedTemporaryFile()
class TestName:
def __init__(self, name: str):
self.name = name
pathlib.Path(name).chmod(0o644) # Case 1: Fix applied, correct results
def test_name(self):
pathlib.Path(self.name).chmod(0o644) # Case 2: Fix applied, correct results
TestName(file.name).test_name()
class TestFd:
def __init__(self, fd: int):
self.fd = fd
os.chmod(fd, 0o644) # Case 3: Fix not applied, correct results
def test_fd(self):
pathlib.Path(self.fd).chmod(0o644) # Case 4: Fix applied, incorrect results!
TestFd(file.fileno()).test_fd()
print("Done")
Summary
If a file descriptor (
int) is used as the first argument ofos.chmod, and the file descriptor isn't explicitly/locally annotated as anint, then thePTH101fix incorrectly replacesos.chmodwithPath.chmod, which only works with file paths (str) and not file descriptors (int), which converts working code into code that raises aTypeErrorat runtime.Playground
Version
ruff 0.15.12