pathlib.Path is incredibly useful. However subclassing it is a bit difficult since Path generates either a WindowsPath or PosixPath ( or equivalent respective PurePath). It may be useful to subclass Path if someone wants to add additional methods / attributes e.g. a required field if one wants to make an expected path element.
from dataclasses import dataclass, field, KW_ONLY
from typing import Optional, Union
from pathlib import Path, PurePath, _PosixFlavour, PosixPath, WindowsPath, _posix_flavour, _Flavour, _windows_flavour
from enum import StrEnum
class OSFlavour(StrEnum):
NT = 'nt'
POSIX = 'posix'
def __str__(self):
_str = self.value
if _str == self.NT: _str = 'windows'
return _str
def __repr__(self):
_str = self.value
if _str == self.NT: _str = 'windows'
return _str
@classmethod
def on_posix():
return os.name == 'posix'
@classmethod
def on_windows():
return os.name == 'nt'
@classmethod
def is_posix(cls, other: Union[str, 'OSFlavour']) -> bool:
if isinstance(other, cls): other = other.value
return other == cls.POSIX and cls.on_posix()
@classmethod
def is_windows(cls, other: Union[str, 'OSFlavour']) -> bool:
if isinstance(other, cls): other = other.value
return other == cls.NT and cls.on_windows()
def am_posix(self):
return OSFlavour.is_posix(self.value)
def am_windows(self):
return OSFlavour.is_windows(self.value)
@classmethod
def get_flavour(cls):
if cls.on_posix(): return _posix_flavour
if cls.on_windows(): return _windows_flavour
raise ValueError(f'os.name {os.name} not recognized')
import os
from dataclasses import dataclass, field
from pathlib import Path, PurePath, PosixPath, WindowsPath
POSIX = 'posix'
NT = 'nt'
def is_posix():
return os.name == 'posix'
def is_windows():
return os.name == 'nt'
def get_flavour():
if is_posix(): return _posix_flavour
if is_windows(): return _windows_flavour
raise ValueError(f'os.name {os.name} not recognized')
@dataclass
class BasePathEntry(PurePath):
required: bool = False
_flavour: '_Flavour' = field(default_factory=get_flavour, init=False, repr=False)
@dataclass
class PosixPathEntry(BasePathEntry, PosixPath):
__repr__ = lambda self: f'PosixPathEntry({self.as_posix()})'
@dataclass
class WindowsPathEntry(BasePathEntry, WindowsPath):
__repr__ = lambda self: f'WindowsPathEntry({self.as_posix()})'
@dataclass
class PathEntry(Path):
_flavour: '_Flavour' = field(default_factory=get_flavour, init=False, repr=False)
@classmethod
def _from_parts(cls, *args, **kwargs):
self = super()._from_parts(*args)
return self
def __new__(cls, *args, **kwargs):
required = kwargs.pop('required', False)
if cls is PathEntry:
cls = WindowsPathEntry if is_windows() else PosixPathEntry
self = cls._from_parts(*args, **kwargs)
self.required = required
if not self._flavour.is_supported:
raise NotImplementedError(f"Cannot instantiate {cls.__name__} on your system")
return self
> Blockquote
Subclassing from pathlib.Path should work fine from Python 3.12 (currently in beta) - your subclass will use the current system’s “flavour”, similar to what happens when you attempt to initialise Path.
In 3.12+, the PurePath._flavour attribute is set to either posixpath or ntpath, which are modules providing os.path on POSIX and Windows respectively. All OS-specific behaviour in pathlib is now driven by this attribute. I think this works quite a lot like the enum you’re suggesting.
I’m hoping to make that public in 3.13, so this sort of thing would be possible:
path = get_a_path_object()
path.flavour is posixpath # is it posix-flavoured?
path.flavour is ntpath # is it windows-flavoured?
path.flavour is os.path # does the flavour match the current OS?
Currently you could do this:
isinstance(path, PurePosixPath) # is it posix-flavoured?
isinstance(path, PureWindowsPath) # is it windows-flavoured?
Hmmm… will there be a need to know all flavours or which flavours are available?
I like this
path = get_a_path_object()
path.flavour is posixpath # is it posix-flavoured?
path.flavour is ntpath # is it windows-flavoured?
path.flavour is os.path # does the flavour match the current OS?
As that was part of the main think I was considering. Having a generic way of checking flavour / comparing them.