Usage Examples
Advanced usage patterns and examples for Apathetic Python Utils.
Configuration File Loading
Loading JSONC Configuration
from apathetic_utils import load_jsonc
from pathlib import Path
# Load a configuration file with comments
config = load_jsonc(Path("config.jsonc"))
if config:
app_name = config.get("name", "default")
version = config.get("version", "1.0.0")
print(f"{app_name} v{version}")
Loading TOML Configuration
from apathetic_utils import load_toml
from pathlib import Path
# Load pyproject.toml
pyproject = load_toml(Path("pyproject.toml"), required=True)
if pyproject:
project_name = pyproject.get("project", {}).get("name", "unknown")
print(f"Project: {project_name}")
Handling Missing Files
from apathetic_utils import load_jsonc, load_toml
from pathlib import Path
# Try to load config, handle gracefully if missing
config = load_jsonc(Path("config.jsonc"))
if config is None:
print("No configuration found, using defaults")
config = {"name": "default", "version": "1.0.0"}
# For TOML, use required=False to return None instead of raising
pyproject = load_toml(Path("pyproject.toml"), required=False)
if pyproject is None:
print("pyproject.toml not available (Python 3.10 without tomli)")
Path Normalization and Glob Patterns
Cross-Platform Path Handling
from apathetic_utils import normalize_path_string, has_glob_chars, get_glob_root
from pathlib import Path
# Normalize Windows paths
windows_path = "src\\utils\\file.py"
normalized = normalize_path_string(windows_path)
print(normalized) # "src/utils/file.py"
# Handle escaped spaces
path_with_spaces = "my\\ project\\ file.py"
normalized = normalize_path_string(path_with_spaces)
print(normalized) # "my project file.py"
# Check for glob patterns
pattern = "src/**/*.py"
if has_glob_chars(pattern):
root = get_glob_root(pattern)
print(f"Glob root: {root}") # Path("src")
Pattern Matching and Exclusion
Recursive Glob Matching
from apathetic_utils import fnmatchcase_portable
# Match files recursively
files = [
"src/main.py",
"src/utils/helper.py",
"src/utils/nested/deep.py",
"tests/test_main.py",
]
pattern = "src/**/*.py"
matched = [f for f in files if fnmatchcase_portable(f, pattern)]
# Returns: ["src/main.py", "src/utils/helper.py", "src/utils/nested/deep.py"]
Exclusion Pattern Checking
from apathetic_utils import is_excluded_raw
from pathlib import Path
# Define exclusion patterns
exclude_patterns = [
"**/__pycache__/**",
"*.pyc",
"*.pyo",
"tests/**",
".git/**",
]
root = Path(".")
# Check if files should be excluded
files_to_check = [
"src/utils/file.py",
"src/__pycache__/file.pyc",
"tests/test_file.py",
".git/config",
]
for file_path in files_to_check:
if is_excluded_raw(file_path, exclude_patterns, root):
print(f"{file_path} is excluded")
else:
print(f"{file_path} is included")
Advanced Exclusion with Relative Paths
from apathetic_utils import is_excluded_raw
from pathlib import Path
# Patterns with ../ to match files outside the root
exclude_patterns = [
"../docs/**", # Exclude docs directory outside project
"**/node_modules/**",
]
root = Path("project")
file_path = Path("../docs/README.md")
if is_excluded_raw(file_path, exclude_patterns, root):
print("File is excluded")
System Detection
CI Environment Detection
from apathetic_utils import is_ci
def setup_environment():
if is_ci():
print("Running in CI environment")
# Use CI-specific settings
log_level = "warning"
enable_debug = False
else:
print("Running locally")
# Use development settings
log_level = "debug"
enable_debug = True
return log_level, enable_debug
Runtime Mode Detection
from apathetic_utils import detect_runtime_mode
def get_build_info():
mode = detect_runtime_mode("my_package")
if mode == "package":
print("Running as package")
elif mode == "stitched":
print("Running as stitched script")
elif mode == "zipapp":
print("Running as zipapp")
elif mode == "frozen":
print("Running as frozen executable")
return mode
Pytest Detection
from apathetic_utils import is_running_under_pytest
def get_config():
if is_running_under_pytest():
# Use test configuration
return {
"database": "test.db",
"log_level": "error",
}
else:
# Use production configuration
return {
"database": "prod.db",
"log_level": "info",
}
Output Capture
Capturing CLI Output
from apathetic_utils import capture_output
import sys
def run_command():
with capture_output() as cap:
print("Processing files...")
print("File 1 processed", file=sys.stderr)
print("File 2 processed")
print("Complete!", file=sys.stderr)
# Access captured output
stdout = cap.stdout.getvalue()
stderr = cap.stderr.getvalue()
merged = cap.merged.getvalue()
return {
"stdout": stdout,
"stderr": stderr,
"merged": merged,
}
Capturing with Exception Handling
from apathetic_utils import capture_output
import sys
def safe_command():
try:
with capture_output() as cap:
# Some operation that might fail
print("Starting operation")
raise ValueError("Something went wrong")
except ValueError as e:
# Captured output is attached to the exception
if hasattr(e, "captured_output"):
print(f"Error occurred. Output: {e.captured_output.merged.getvalue()}")
raise
Converting to Dictionary
from apathetic_utils import capture_output
import sys
with capture_output() as cap:
print("Output line 1")
print("Output line 2", file=sys.stderr)
# Convert to dictionary for serialization
output_dict = cap.as_dict()
# {
# "stdout": "Output line 1\n",
# "stderr": "Output line 2\n",
# "merged": "Output line 1\nOutput line 2\n"
# }
Text Processing
Pluralization
from apathetic_utils import plural
def format_count(items):
count = len(items)
return f"{count} item{plural(items)}"
print(format_count([1, 2, 3])) # "3 items"
print(format_count([1])) # "1 item"
print(format_count([])) # "0 items"
# Works with numbers too
print(f"5 file{plural(5)}") # "5 files"
print(f"1 file{plural(1)}") # "1 file"
Error Message Cleanup
from apathetic_utils import remove_path_in_error_message, load_jsonc
from pathlib import Path
def load_config_safely(path: Path):
try:
return load_jsonc(path)
except ValueError as e:
# Remove redundant path from error message
clean_msg = remove_path_in_error_message(str(e), path)
raise ValueError(f"Failed to load config: {clean_msg}") from e
# Usage
try:
config = load_config_safely(Path("config.jsonc"))
except ValueError as e:
print(e) # "Failed to load config: Invalid JSONC syntax: Expecting value"
Type Utilities
Safe Type Checking
from apathetic_utils import safe_isinstance
from typing import TypedDict, Optional, Literal
class Config(TypedDict):
name: str
value: int
optional_field: Optional[str]
# Check TypedDict
data = {"name": "test", "value": 42}
if safe_isinstance(data, Config):
print("Valid config")
# Check generics
items = [1, 2, 3]
if safe_isinstance(items, list[int]):
print("List of integers")
# Check Optional
value: Optional[str] = None
if safe_isinstance(value, Optional[str]):
print("Valid optional value")
Literal Type Validation
from apathetic_utils import literal_to_set
from typing import Literal
Mode = Literal["dev", "prod", "test"]
def validate_mode(mode: str) -> bool:
valid_modes = literal_to_set(Mode)
return mode in valid_modes
# Usage
if validate_mode("dev"):
print("Valid mode")
else:
print("Invalid mode")
TypedDict Schema Extraction
from apathetic_utils import schema_from_typeddict
from typing import TypedDict
class UserConfig(TypedDict):
username: str
email: str
age: int
# Extract schema
schema = schema_from_typeddict(UserConfig)
# Returns: {"username": str, "email": str, "age": int}
# Use for validation or documentation
for field, field_type in schema.items():
print(f"{field}: {field_type}")
Complete CLI Application Example
A complete example combining multiple utilities:
#!/usr/bin/env python3
"""Example CLI application using Apathetic Python Utils."""
import sys
from pathlib import Path
from apathetic_utils import (
load_jsonc,
load_toml,
is_ci,
detect_runtime_mode,
capture_output,
plural,
normalize_path_string,
)
def main():
# Detect environment
if is_ci():
print("Running in CI environment")
mode = detect_runtime_mode("my_app")
print(f"Runtime mode: {mode}")
# Load configuration
config_path = Path("config.jsonc")
if config_path.exists():
config = load_jsonc(config_path)
if config:
print(f"Loaded config: {config.get('name', 'unknown')}")
# Process files
files = ["file1.py", "file2.py", "file3.py"]
print(f"Processing {len(files)} file{plural(files)}...")
# Capture output from processing
with capture_output() as cap:
for file in files:
normalized = normalize_path_string(file)
print(f"Processing {normalized}")
print(f"Output:\n{cap.merged.getvalue()}")
if __name__ == "__main__":
main()
Integration with Build Tools
Using with serger
from apathetic_utils import detect_runtime_mode, load_jsonc
from pathlib import Path
def get_build_config():
mode = detect_runtime_mode("my_package")
if mode == "stitched":
# Load stitched-specific config
config = load_jsonc(Path(".serger.jsonc"))
return config.get("stitched", {})
else:
# Use package config
return {}
Next Steps
- API Reference — Complete API documentation
- Quick Start Guide — Get up and running quickly
- Contributing — Learn how to contribute