Skip to content

Instantly share code, notes, and snippets.

@sidisinsane
Created May 22, 2025 12:47
Show Gist options
  • Select an option

  • Save sidisinsane/e32c4e8d778255ff2ba913d5b2077cbf to your computer and use it in GitHub Desktop.

Select an option

Save sidisinsane/e32c4e8d778255ff2ba913d5b2077cbf to your computer and use it in GitHub Desktop.
Utility functions for common file and folder operations.
"""
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
"""
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