Created
May 22, 2025 12:47
-
-
Save sidisinsane/e32c4e8d778255ff2ba913d5b2077cbf to your computer and use it in GitHub Desktop.
Utility functions for common file and folder operations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| src/YOUR_MODULE_NAME/utils/fs_utils.py | |
| Utility functions for common file and folder operations. | |
| Currently includes: | |
| - IgnorePatternsError: Exception for handling load/parse failures. | |
| - load_ignore_patterns: Read and parse ignore-files into a list of patterns. | |
| - parse_ignore_patterns: Extract non-comment, non-blank lines from raw file content. | |
| - JSONLoadError: Exception for JSON loading failures. | |
| - load_json: Safely load and parse a JSON file into a Python object. | |
| Future utilities (e.g., directory traversal, file validation) can be added here to keep | |
| all filesystem-related helpers organized in one module. | |
| """ | |
| import json | |
| from pathlib import Path | |
| from typing import Any, List | |
| class IgnorePatternsError(Exception): | |
| """Base exception for ignore-pattern loading/parsing failures.""" | |
| pass | |
| class JSONLoadError(Exception): | |
| """Exception raised when JSON file loading or parsing fails.""" | |
| pass | |
| def load_ignore_patterns( | |
| filepath: Path, | |
| encoding: str = "utf-8", | |
| ) -> List[str]: | |
| """ | |
| Read an ignore-file and return its non-comment, non-blank lines. | |
| Args: | |
| filepath (Path): Path to a file containing ignore patterns. | |
| encoding (str): File encoding. Defaults to "utf-8". | |
| Returns: | |
| List[str]: A list of patterns (whitespace trimmed). | |
| Raises: | |
| IgnorePatternsError: If the file cannot be read or parsed. | |
| Example: | |
| >>> from fs_utils import load_ignore_patterns | |
| >>> patterns = load_ignore_patterns(Path(".gitignore")) | |
| """ | |
| try: | |
| text = filepath.read_text(encoding=encoding) | |
| except (OSError, UnicodeDecodeError) as e: | |
| raise IgnorePatternsError(f"Failed to load {filepath!r}: {e}") from e | |
| try: | |
| return parse_ignore_patterns(text) | |
| except Exception as e: | |
| # This is unlikely unless parse_ignore_patterns itself changes. | |
| raise IgnorePatternsError(f"Failed to parse {filepath!r}: {e}") from e | |
| def parse_ignore_patterns(content: str) -> List[str]: | |
| """ | |
| Extract non-comment, non-blank lines from raw file content. | |
| Args: | |
| content (str): The full text of an ignore file. | |
| Returns: | |
| List[str]: A list of trimmed patterns. | |
| Raises: | |
| None | |
| """ | |
| # Strip out blank lines and lines starting with '#' (after whitespace) | |
| return [ | |
| line.strip() | |
| for line in content.splitlines() | |
| if (stripped := line.strip()) and not stripped.startswith("#") | |
| ] | |
| def load_json( | |
| filepath: Path, | |
| encoding: str = "utf-8", | |
| ) -> Any: | |
| """ | |
| Safely load and parse a JSON file into a Python object. | |
| Args: | |
| filepath (Path): Path to the JSON file to read. | |
| encoding (str): File encoding. Defaults to "utf-8". | |
| Returns: | |
| Any: The Python object resulting from JSON deserialization. | |
| Raises: | |
| JSONLoadError: If the file cannot be read or JSON is invalid. | |
| Example: | |
| >>> from utils.fs_utils import load_json | |
| >>> data = load_json(Path("config.json")) | |
| >>> # Use data as a dict/list based on JSON structure | |
| """ | |
| try: | |
| text = filepath.read_text(encoding=encoding) | |
| except (OSError, UnicodeDecodeError) as e: | |
| raise JSONLoadError(f"Failed to load file {filepath!r}: {e}") from e | |
| try: | |
| return json.loads(text) | |
| except json.JSONDecodeError as e: | |
| raise JSONLoadError(f"Invalid JSON in file {filepath!r}: {e}") from e |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| tests/utils/test_fs_utils.py | |
| Unit tests for fs_utils filesystem utility functions. | |
| Tests: | |
| - load_ignore_patterns: Valid patterns and error handling. | |
| - parse_ignore_patterns: Extracts correct lines from sample content. | |
| - load_json: Valid JSON load and JSONLoadError on invalid or unreadable files. | |
| Run with: | |
| $ pytest tests/utils/test_fs_utils.py | |
| """ | |
| import json | |
| from textwrap import dedent | |
| import pytest | |
| from YOUR_MODULE_NAME.utils.fs_utils import ( | |
| IgnorePatternsError, | |
| JSONLoadError, | |
| load_ignore_patterns, | |
| load_json, | |
| parse_ignore_patterns, | |
| ) | |
| def test_parse_ignore_patterns_basic(): | |
| content = """ | |
| # comment line | |
| pattern1 | |
| pattern2 | |
| # another comment | |
| """ | |
| expected = ["pattern1", "pattern2"] | |
| assert parse_ignore_patterns(dedent(content)) == expected | |
| def test_load_ignore_patterns(tmp_path): | |
| file_path = tmp_path / "test.ignore" | |
| file_path.write_text("#ignore me\npatA\npatB\n") | |
| patterns = load_ignore_patterns(file_path) | |
| assert patterns == ["patA", "patB"] | |
| def test_load_ignore_patterns_file_error(tmp_path): | |
| missing = tmp_path / "noexist.ignore" | |
| with pytest.raises(IgnorePatternsError) as excinfo: | |
| load_ignore_patterns(missing) | |
| assert "Failed to load" in str(excinfo.value) | |
| def test_load_json_valid(tmp_path): | |
| data = {"a": 1, "b": [2, 3]} | |
| file_path = tmp_path / "data.json" | |
| file_path.write_text(json.dumps(data), encoding="utf-8") | |
| result = load_json(file_path) | |
| assert result == data | |
| def test_load_json_invalid(tmp_path): | |
| file_path = tmp_path / "bad.json" | |
| file_path.write_text("{invalid json}") | |
| with pytest.raises(JSONLoadError) as excinfo: | |
| load_json(file_path) | |
| assert "Invalid JSON" in str(excinfo.value) | |
| def test_load_json_file_error(tmp_path): | |
| missing = tmp_path / "no.json" | |
| with pytest.raises(JSONLoadError) as excinfo: | |
| load_json(missing) | |
| assert "Failed to load file" in str(excinfo.value) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment