#pragma preserve-exports
## @file
# Copyright (c) 2023, Cory Bennett. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
##
"""Overrides the `pathlib` module to support backports and custom subclasses."""
#pylint: disable=C0103,E0602,E1003,R1725,W0718
import pathlib
from inspect import signature
from pathlib import PosixPath, PurePosixPath, PureWindowsPath, WindowsPath
from typing import Any, List, Optional, TypeVar, Union
from .. import inject_module_namespace
class BasePath():
"""Provides a `pathlib` wrapper class that can be subclassed idiomatically."""
TBasePath = TypeVar("TBasePath", bound="BasePath")
"""BasePath class type alias for overriding built-in method signatures.
@internal
"""
cls_flavour: TBasePath
subclasses: List[TBasePath] = []
def __init__(self: TBasePath, *args, **kwargs):
# Ensure MRO is cooperative with subclassing
super(BasePath, self).__init__()
# Attempt to subclass pathlib.Path directly - Python 3.12+
# @see https://github.com/Qonfused/OCE-Build/pull/4#issuecomment-1611019621
try:
super(self.cls_flavour, self).__init__(*args)
# Instantiates a new Path subclass using the `__new__` method.
# This uses a __cls__ hook as a fallback for the `__getattribute__` method.
except Exception: #pragma: no cover
self.__cls__ = super().__new__(self.cls_flavour, *args, **kwargs)
@classmethod
def __init_subclass__(cls: TBasePath,
/,
as_flavour: Optional[any]=None,
**kwargs):
"""Initializes a new subclass of a `pathlib` module baseclass.
Args:
cls: The subclass to initialize.
as_flavour: The `pathlib` flavour class to subclass.
**kwargs: Additional keyword arguments to pass to the superclass.
"""
super().__init_subclass__(**kwargs)
# If specified, override the Path/PurePath flavour subclass
if as_flavour:
cls.cls_flavour = as_flavour
# Otherwise append Path/PurePath subclasses
elif cls.__bases__[0] != BasePath:
cls.subclasses.append(cls)
def __getattribute__(self: TBasePath, __name: str) -> Any:
"""Retrieves an attribute from the instantiated class or subclass."""
self_attr = super().__getattribute__(__name)
# Attempt to retrieve overridden class attributes
try: #pragma: no cover
# Get the uninstantiated class representation
class_repr = super().__getattribute__('__class__')
# Get the instantiated subclass attribute (if either exists)
cls_ref = super().__getattribute__('__cls__')
cls_attr = cls_ref.__getattribute__(__name)
# Only return attribute if not overridden
assert not class_repr().__getattribute__(__name)
assert signature(self_attr) == signature(cls_attr)
# Otherwise return the class attribute
return cls_attr
except Exception: #pragma: no cover
pass
# Fallback to the class attribute (if it exists)
return self_attr
def __getinstance__(self: TBasePath) -> TBasePath:
"""Retrieves the currently instantiated concrete path baseclass."""
cls_instance: self.cls_flavour
try: #pragma: no cover
# Check if `pathlib.Path` has called the `__init__` method - Python 3.12+
if '_raw_paths' in dir(self):
cls_instance = super(self.cls_flavour, self)
# Fall back to calling initialized `__cls__` subclass
elif '__cls__' in dir(self):
cls_instance = self.__cls__
# Fall back to initializing and calling a new path flavour subclass
else:
cls_instance = self.cls_flavour(self)
except Exception as e: #pragma: no cover
raise RuntimeError("Unable to retrieve instantiated path class.") from e
return cls_instance
[docs]class Path(BasePath, Path_flavour := type(pathlib.Path())):
"""Provides a `pathlib.Path` class that can be subclassed idiomatically."""
[docs] TPath = TypeVar("TPath", bound="Path")
"""Path class type alias for overriding built-in method signatures.
@internal
"""
[docs] cls_flavour = Path_flavour
[docs] subclasses: List[TPath] = []
[docs] def relative(self: TPath,
path: Union[str, TPath]='.',
from_parent: bool=False
) -> str:
"""Resolves a relative representation from a file or directory path."""
parent_dir = Path(path).resolve()
if from_parent and self.resolve().is_file():
parent_dir = parent_dir.parent
return self.resolve().relative_to(parent_dir).as_posix()
[docs]class PurePath(BasePath, PurePath_flavour := type(pathlib.PurePath())):
"""Provides a `pathlib.PurePath` class that can be subclassed idiomatically."""
[docs] TPurePath = TypeVar("TPurePath", bound="PurePath")
"""PurePath class type alias for overriding built-in method signatures.
@internal
"""
[docs] cls_flavour = PurePath_flavour
[docs] subclasses: List[TPurePath] = []
# Inject the `pathlib` namespace into the current module
inject_module_namespace(pathlib, namespace=globals())
__all__ = [
"Path",
"PurePath",
"PosixPath",
"PurePosixPath",
"WindowsPath",
"PureWindowsPath"
]