Source code for scholar_flux.package_metadata.directories

# /package_metadata/directories.py
"""The scholar_flux.package_metadata.directories module implements `PackageDirectorySettings` for directory settings.

This model is initialized on package startup at the package level, using the
`PackageDirectorySettings.get_default_writable_directory` method to determine the default directory to use for caching
and logging based on whether it is writable.

"""
from pathlib import Path
from typing import ClassVar, Literal, Optional

import os

from pydantic import BaseModel, Field


[docs] class PackageDirectorySettings(BaseModel): """Directory configuration settings used during package initialization to determine writable package directories. Args: hidden_directory_name (str): The name of the hidden directory for package data where files will be stored. package_directory (Path): The path to the `scholar-flux` package directory. home_env_var (str): The name of the environment variable used to determine the parent directory for the package. """ hidden_directory_name: str = Field( default_factory=lambda: f".{PackageDirectorySettings.DEFAULT_PACKAGE_NAME}", description="The default parent directory name used for storing logs/cache", ) package_directory: Path = Field( default_factory=lambda: PackageDirectorySettings.DEFAULT_PACKAGE_SOURCE_DIRECTORY, description="The location of the package directory where source code and tests are stored.", ) home_env_var: str = Field( default="SCHOLAR_FLUX_HOME", description="Environment variable defining the user-specified cache/log directory" ) DEFAULT_PACKAGE_SOURCE_DIRECTORY: ClassVar[Path] = Path(__file__).parent.parent DEFAULT_PACKAGE_NAME: ClassVar[str] = "scholar_flux" @property def package_env_home(self) -> Optional[Path]: """Resolves the user-specified package `home_env` variable for storing logs, caching, and configuration.""" env_home = os.getenv(self.home_env_var) return Path(env_home) if env_home else None def _get_default_readable_directory_candidates(self) -> list[Path]: """Returns candidate parent directories in priority order for read operations.""" env_home = self.package_env_home candidates = [ Path.cwd(), Path.home() / self.hidden_directory_name, Path.cwd() / self.hidden_directory_name, self.package_directory, ] return [env_home] + candidates if env_home is not None else candidates def _get_default_writable_directory_candidates(self, directory_type: str) -> list[Path]: """Returns potentially writable candidate parent directories in priority order.""" if directory_type == "env": return self._get_default_readable_directory_candidates() env_home = self.package_env_home hidden_directories = [ Path.home() / self.hidden_directory_name, Path.cwd() / self.hidden_directory_name, self.package_directory, ] return [env_home] + hidden_directories if env_home else hidden_directories
[docs] @classmethod def verify_directory(cls, path: str | Path, create_parent_directories: bool = False) -> Path: """Uses `pathlib.Path` to verify that the current directory is writable by attempting to create the directory. Args: path (str| Path): The directory to check. The string paths are converted into a `pathlib.Path` objects before directory verification. create_parent_directories (bool): Indicates whether parent directories should be created if they do not already exist. Returns: Path: The original path if the directory is writable. Raises: PermissionError: If the directory is not writable due to a permissions error. OSError: If an OSError occurs when attempting to create the directory. TypeError: If an incorrect type is passed to `Path` or `Path.mkdir`. """ current_path = Path(path) if not isinstance(path, Path) else path current_path.mkdir(parents=create_parent_directories, exist_ok=True) return current_path
[docs] def get_default_writable_directory( self, directory_type: Literal["package_cache", "logs", "env"], subdirectory: Optional[str | Path] = None, *, default: Optional[Path] = None, ) -> Path: """Determines the default directory to use for storing package cache, logs, and environment variables. In the case where a default directory is not specified for caching and logging in package-specific functionality, this method serves as a fallback, identifying writable package directories when required. Args: directory_type (Literal['package_cache','logs', "env"]): The functionality that a writable directory is being created for. subdirectory (Optional[str | Path]): The name or folder path to create within the default directory. By default, the scholar_flux package creates `package_cache` and `logs` subdirectories for caching and logging, respectively, while environment variables are read directly from a `.env` file if placed within the parent directory. default (Optional[Path]): Defines an optional path to use when none of the default directories are available. If None, this function will raise a `RuntimeError` when the package default directories are not writable. Returns: Path: The path of a default writable directory if found. Raises: RuntimeError if a writable directory cannot be identified. """ if directory_type not in ("package_cache", "logs", "env"): raise ValueError( f"Received an incorrect directory_type ({directory_type}) when identifying writable directories." ) for base_path in self._get_default_writable_directory_candidates(directory_type): try: # default to the `package_cache` and `logs` subdirectory names if `subdirectory` isn't specified # otherwise uses the parent directory current_subdirectory = subdirectory or ( directory_type if directory_type in ("package_cache", "logs") else Path() ) full_path = base_path / current_subdirectory create_parent_directories = directory_type != "env" # Test writability if `create_parent_directories` is True return self.verify_directory(full_path, create_parent_directories=create_parent_directories) except (PermissionError, OSError, TypeError): continue if default: return Path(default) raise RuntimeError(f"Could not locate a writable {directory_type} directory for {self.DEFAULT_PACKAGE_NAME}")
__all__ = ["PackageDirectorySettings"]