13
[Solved] Subclassing pathlib.PosixPath broken since Python 3.12 (actually its fixed, but workaround broken)
(beehaw.org)
submitted
6 months ago* (last edited 6 months ago)
by
thingsiplay@beehaw.org
to
c/programming@programming.dev
Solution was quite easy. Thanks to the user reply here: beehaw.org/comment/3535588 or programming.dev/comment/10034690 (Not sure if the links actually work as expected...)
Hi all. I have a little problem and don't know how to solve. A CLI program in Python is broken since Python 3.12. It was working in Python 3.11. The reason is, that Python 3.12 changed how subclassing of a pathlib.Path works (basically fixed an issue), which now breaks a workaround.
The class in question is:
class File(PosixPath):
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
return cls._from_parts(args).expanduser().resolve() # type: ignore
def __init__(self, source: str | Path, *args: Any) -> None:
super().__init__()
self.__source = Path(source)
@property
def source(self) -> Path:
return self.__source
@property
def modified(self) -> Time:
return Time.fromtimestamp(os.path.getmtime(self))
@property
def changed(self) -> Time:
return Time.fromtimestamp(os.path.getctime(self))
@property
def accessed(self) -> Time:
return Time.fromtimestamp(os.path.getatime(self))
# Calculate sha512 hash of self file and compare result to the
# checksum found in given file. Return True if identical.
def verify_sha512(self, file: File, buffer_size: int = 4096) -> bool:
compare_hash: str = file.read_text().split(" ")[0]
self_hash: str = ""
self_checksum = hashlib.sha512()
with open(self.as_posix(), "rb") as f:
for chunk in iter(lambda: f.read(buffer_size), b""):
self_checksum.update(chunk)
self_hash = self_checksum.hexdigest()
return self_hash == compare_hash
and I get this error when running the script:
Traceback (most recent call last):
File "/home/tuncay/.local/bin/geprotondl", line 1415, in
sys.exit(main())
^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1334, in main
arguments, status = parse_arguments(argv)
^^^^^^^^^^^^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1131, in parse_arguments
default, status = default_install_dir()
^^^^^^^^^^^^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1101, in default_install_dir
steam_root: File = File(path)
^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 97, in __new__
return cls._from_parts(args).expanduser().resolve() # type: ignore
^^^^^^^^^^^^^^^
AttributeError: type object 'File' has no attribute '_from_parts'. Did you mean: '_load_parts'?
Now replacing _from_parts
with _load_parts
does not work either and I get this message in that case:
Traceback (most recent call last):
File "/home/tuncay/.local/bin/geprotondl", line 1415, in
sys.exit(main())
^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1334, in main
arguments, status = parse_arguments(argv)
^^^^^^^^^^^^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1131, in parse_arguments
default, status = default_install_dir()
^^^^^^^^^^^^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 1101, in default_install_dir
steam_root: File = File(path)
^^^^^^^^^^
File "/home/tuncay/.local/bin/geprotondl", line 97, in __new__
return cls._load_parts(args).expanduser().resolve() # type: ignore
^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/pathlib.py", line 408, in _load_parts
paths = self._raw_paths
^^^^^^^^^^^^^^^
AttributeError: 'tuple' object has no attribute '_raw_paths'
What’s the constructor in
PosixPath
look like?From https://github.com/python/cpython/blob/3.12/Lib/pathlib.py
Which sub classes PurePosixPath, which itself is basically just PurePath with a Posix flavour to it. And Path itself is PurePath as well. Not sure if I should copy paste them here.
Edit: So I added these to my reply:
PurePath
Path
What I’m guessing is that these things have a different loader you want to use. You’re headed in the right direction here. The “no method” error just means you have to trace what you need.
You could also look at something like pipx or a virtualenv to guarantee an older version of Python.
The other person got me the solution.
__new__()
is no longer needed to be overwritten anymore.