Files for humans.
Project description
Easy File
Files for humans.
Easy File is a modern, type-safe file operations library that extends Python's pathlib.Path with powerful features like fast JSON/YAML serialization, atomic writes, async support, and automatic directory creation.
Why Easy File?
# Standard library
import pathlib
import json
path = pathlib.Path("config/app.json")
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
json.dump({"name": "My App"}, f, indent=2)
# Easy File
from easy_file import File
File("config/app.json").dump_json({"name": "My App"})
Features
🚀 Performance
- 1.5-2× faster JSON/YAML operations using msgspec
- Efficient async I/O with
asyncio.to_thread()
🛡️ Safety
- Atomic writes guarantee data integrity
- Type-safe deserialization with TypedDict and dataclasses
- UTF-8 by default - no more encoding headaches
🎯 Convenience
- Automatic directory creation - no more
mkdir(parents=True) - Intuitive API - method chaining and fluent interfaces
- Full pathlib compatibility - drop-in replacement for
Path
⚡ Modern Python
- Full async/await support
- Type hints everywhere
- Python 3.12+ compatible
Installation
pip install easy_file
Or with uv:
uv add easy_file
Quick Start
Basic File Operations
from easy_file import File
# Read and write text (UTF-8 by default)
f = File("data.txt")
f.write_text("Hello World!")
content = f.read_text() # "Hello World!"
# Binary operations
f.write_bytes(b"\x00\x01\x02")
data = f.read_bytes() # b"\x00\x01\x02"
# Append text
f.append_text("\nNew line")
# File size
print(f"File size: {f.size} bytes")
JSON Operations
# Simple JSON
config = File("config.json")
config.dump_json({
"name": "My App",
"version": "1.0.0",
"debug": True
})
data = config.load_json()
print(data["name"]) # "My App"
# Compact JSON (no formatting)
config.dump_json(data, indent=0)
YAML Operations
# YAML serialization
settings = File("settings.yaml")
settings.dump_yaml({
"database": {
"host": "localhost",
"port": 5432
},
"cache": {
"enabled": True,
"ttl": 3600
}
})
data = settings.load_yaml()
print(data["database"]["host"]) # "localhost"
File Management
# Copy files (with method chaining)
source = File("document.txt").write_text("Important data")
backup = source.copy("backup/document.txt")
# Move/rename files
archived = source.move("archive/old_document.txt")
# Create file with parent directories
File("deep/nested/path/file.txt").touch_parents()
Type-Safe Deserialization
Easy File supports typed deserialization with full IDE autocomplete and runtime validation.
Using TypedDict
from typing import TypedDict
from easy_file import File
class AppConfig(TypedDict):
name: str
version: str
debug: bool
max_connections: int
# Save config
config = File("config.json")
config.dump_json({
"name": "My App",
"version": "2.0.0",
"debug": False,
"max_connections": 100
})
# Load with type safety
data: AppConfig = config.load_json(AppConfig)
# IDE autocomplete works!
print(data["name"]) # ✓ Type-safe
print(data["invalid"]) # ✗ IDE warning
Using Dataclasses
from dataclasses import dataclass
from easy_file import File
@dataclass
class User:
id: int
name: str
email: str
is_active: bool = True
# Save user
user_file = File("user.json")
user_file.dump_json({
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"is_active": True
})
# Load with validation
user: User = user_file.load_json(User)
# Access as object attributes
print(user.name) # "Alice"
print(user.email) # "alice@example.com"
Works with YAML too!
from typing import TypedDict
class DatabaseConfig(TypedDict):
host: str
port: int
username: str
password: str
db_config = File("database.yaml")
db_config.dump_yaml({
"host": "localhost",
"port": 5432,
"username": "admin",
"password": "secret"
})
config: DatabaseConfig = db_config.load_yaml(DatabaseConfig)
print(f"Connecting to {config['host']}:{config['port']}")
Async Operations
All I/O operations have async counterparts for non-blocking execution.
Async File I/O
import asyncio
from easy_file import File
async def main():
f = File("async_data.txt")
# Text operations
await f.write_text_async("Hello async world!")
content = await f.read_text_async()
await f.append_text_async("\nMore content")
# Binary operations
await f.write_bytes_async(b"\x00\x01\x02")
data = await f.read_bytes_async()
# File management
await f.copy_async("backup.txt")
await f.move_async("archive/data.txt")
asyncio.run(main())
Async JSON/YAML
async def process_config():
config = File("config.json")
# Save config asynchronously
await config.dump_json_async({
"api_key": "secret",
"timeout": 30
})
# Load and process
data = await config.load_json_async()
print(f"Timeout: {data['timeout']}s")
asyncio.run(process_config())
Parallel File Reading
Read multiple files concurrently - perfect for batch processing:
async def read_all_logs():
log_files = [
"logs/app.log",
"logs/error.log",
"logs/access.log",
"logs/debug.log"
]
# Read all files in parallel
contents = await File.read_many_async(log_files)
for filename, content in zip(log_files, contents):
print(f"{filename}: {len(content)} bytes")
asyncio.run(read_all_logs())
Atomic Writes
Data integrity is critical. Easy File uses atomic writes to prevent data corruption.
Automatic Atomic Writes
JSON and YAML operations use atomic writes by default:
# These operations are atomic
config = File("config.json")
config.dump_json({"critical": "data"}) # ✓ Atomic
settings = File("settings.yaml")
settings.dump_yaml({"important": "config"}) # ✓ Atomic
Manual Atomic Writes
Use the atomic_write context manager for custom atomic operations:
from easy_file import File
important_file = File("critical_data.txt")
# Write atomically
with important_file.atomic_write() as f:
f.write("Critical information\n")
f.write("Must be written completely\n")
# If any error occurs here, original file is unchanged
# Binary atomic write
with important_file.atomic_write(mode="wb") as f:
f.write(b"Binary critical data")
How It Works
- Writes to a temporary file in the same directory
- Flushes and syncs to disk
- Atomically replaces the target file
- Cleans up temp file on errors
This ensures your data is never corrupted by crashes or errors during writes.
Error Handling
Easy File provides custom exceptions for clear error handling:
from easy_file import File, JSONDecodeError, YAMLDecodeError, FileOperationError
try:
config = File("config.json").load_json()
except JSONDecodeError as e:
print(f"Invalid JSON format: {e}")
except FileNotFoundError:
print("Config file not found")
except FileOperationError as e:
print(f"File operation failed: {e}")
Exception Hierarchy
Exception
└── FileOperationError # Base class for all Easy File errors
├── JSONDecodeError # JSON parsing failed
└── YAMLDecodeError # YAML parsing failed
Utility Methods
Create Files and Directories
# Create file with all parent directories
File("deep/nested/path/file.txt").touch_parents()
# Alternative using write methods (creates parents automatically)
File("auto/created/dirs/data.json").dump_json({"auto": True})
Append to Files
log = File("app.log")
# Append text (creates file if needed)
log.append_text("Application started\n")
log.append_text("Processing data\n")
log.append_text("Application finished\n")
# Async append
await log.append_text_async("Async log entry\n")
Get File Information
f = File("document.txt")
f.write_text("Hello World!")
print(f"Size: {f.size} bytes") # 12 bytes
print(f"Name: {f.name}") # document.txt
print(f"Extension: {f.suffix}") # .txt
print(f"Parent: {f.parent}") # Current directory
print(f"Absolute: {f.absolute()}") # Full path
Complete API Reference
File Creation
File(path)- Create a File object
Text Operations
read_text(encoding='utf-8')- Read text from filewrite_text(data, encoding='utf-8')- Write text to fileappend_text(text, encoding='utf-8')- Append text to fileread_text_async(encoding='utf-8')- Async text readwrite_text_async(data, encoding='utf-8')- Async text writeappend_text_async(text, encoding='utf-8')- Async append
Binary Operations
read_bytes()- Read bytes from filewrite_bytes(data)- Write bytes to fileread_bytes_async()- Async bytes readwrite_bytes_async(data)- Async bytes write
JSON Operations
load_json(type=None)- Load JSON (optionally typed)dump_json(data, indent=2)- Save JSON with formattingload_json_async(type=None)- Async JSON loaddump_json_async(data, indent=2)- Async JSON save
YAML Operations
load_yaml(type=None)- Load YAML (optionally typed)dump_yaml(data)- Save YAMLload_yaml_async(type=None)- Async YAML loaddump_yaml_async(data)- Async YAML save
File Management
copy(target, preserve_metadata=True)- Copy filemove(target)- Move/rename filecopy_async(target, preserve_metadata=True)- Async copymove_async(target)- Async movetouch_parents()- Create file and parent directoriesopen(mode, encoding=None)- Open file (UTF-8 default)atomic_write(mode='w', encoding=None)- Atomic write context manager
Batch Operations
File.read_many_async(paths)- Read multiple files in parallel (class method)
Properties
size- File size in bytes
Inherited from pathlib.Path
All standard pathlib.Path methods and properties are available:
exists(),is_file(),is_dir()name,stem,suffix,parentabsolute(),resolve(),relative_to()glob(),rglob(),iterdir()chmod(),stat(),unlink()- And many more!
Real-World Examples
Configuration Management
from easy_file import File
from typing import TypedDict
class Config(TypedDict):
api_key: str
base_url: str
timeout: int
debug: bool
# Load config with validation
config_file = File("config.json")
if not config_file.exists():
# Create default config
config_file.dump_json({
"api_key": "",
"base_url": "https://api.example.com",
"timeout": 30,
"debug": False
})
config: Config = config_file.load_json(Config)
Logging System
from datetime import datetime
from easy_file import File
class Logger:
def __init__(self, log_file: str):
self.log = File(log_file)
self.log.touch_parents()
def info(self, message: str):
timestamp = datetime.now().isoformat()
self.log.append_text(f"[{timestamp}] INFO: {message}\n")
def error(self, message: str):
timestamp = datetime.now().isoformat()
self.log.append_text(f"[{timestamp}] ERROR: {message}\n")
logger = Logger("logs/app.log")
logger.info("Application started")
logger.error("Something went wrong")
Data Processing Pipeline
import asyncio
from easy_file import File
async def process_data_files():
# Read all input files in parallel
input_files = ["data1.json", "data2.json", "data3.json"]
data_list = await File.read_many_async(input_files)
# Process each file
results = []
for data_str in data_list:
import json
data = json.loads(data_str)
# Process data...
results.append({"processed": True, "count": len(data)})
# Save results atomically
output = File("results/summary.json")
await output.dump_json_async({
"total_files": len(input_files),
"results": results
})
asyncio.run(process_data_files())
Backup System
from easy_file import File
from datetime import datetime
def backup_config(source_path: str):
source = File(source_path)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"backup/{source.stem}_{timestamp}{source.suffix}"
# Create backup with metadata preserved
backup = source.copy(backup_name, preserve_metadata=True)
print(f"Backed up to: {backup}")
backup_config("config.json")
Migration Guide
From pathlib.Path
# Before (pathlib)
from pathlib import Path
p = Path("data.txt")
p.write_text("content", encoding="utf-8")
content = p.read_text(encoding="utf-8")
# After (easy_file)
from easy_file import File
f = File("data.txt")
f.write_text("content") # UTF-8 by default
content = f.read_text() # UTF-8 by default
From open() + json
# Before
import json
from pathlib import Path
path = Path("config.json")
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2)
with open(path, 'r', encoding='utf-8') as f:
config = json.load(f)
# After
from easy_file import File
File("config.json").dump_json(data)
config = File("config.json").load_json()
Performance
Easy File is built for speed using msgspec:
- JSON: ~1.5-2× faster than standard
jsonmodule - YAML: ~1.5-2× faster than PyYAML
- Minimal overhead: Thin wrapper around msgspec and pathlib
- Efficient async: Uses
asyncio.to_thread()for optimal performance
Requirements
- Python 3.12+
- msgspec >= 0.18.0
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Install development dependencies (
uv sync) - Make your changes and add tests
- Run tests (
uv run pytest) - Run type checking (
uv run mypy src/) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Links
- GitHub: https://github.com/ruslan-rv-ua/easy_file
- PyPI: https://pypi.org/project/easy_file/
- Issues: https://github.com/ruslan-rv-ua/easy_file/issues
Changelog
See CHANGELOG.md for version history and release notes.
Made with ❤️ by Ruslan Iskov
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file easy_file-0.4.1.tar.gz.
File metadata
- Download URL: easy_file-0.4.1.tar.gz
- Upload date:
- Size: 50.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0bce579f941a96742605cba51030258ae9fd2131180b95dadd2ae5994b8b99d
|
|
| MD5 |
d50db5dc23156342027d2d497a9e053b
|
|
| BLAKE2b-256 |
307162adfe2892d6f016b5434fd0bec5625c986e2099b3fcfeb3c391fe8b1441
|
Provenance
The following attestation bundles were made for easy_file-0.4.1.tar.gz:
Publisher:
release.yml on ruslan-rv-ua/easy_file
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easy_file-0.4.1.tar.gz -
Subject digest:
e0bce579f941a96742605cba51030258ae9fd2131180b95dadd2ae5994b8b99d - Sigstore transparency entry: 779443557
- Sigstore integration time:
-
Permalink:
ruslan-rv-ua/easy_file@b4cfdfaf522877df34832250d9152e7f5e8f1d49 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ruslan-rv-ua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b4cfdfaf522877df34832250d9152e7f5e8f1d49 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file easy_file-0.4.1-py3-none-any.whl.
File metadata
- Download URL: easy_file-0.4.1-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
74ffc15f1d8771c282c0619be986d0783ff867b70a52bb015bb0d524d4a71b96
|
|
| MD5 |
26e5ea974b6472973ea93c387364b474
|
|
| BLAKE2b-256 |
1ae66db1bc5b18e6fafe2e433e4bfb5340c048006ddd3e44973ddb0688a34532
|
Provenance
The following attestation bundles were made for easy_file-0.4.1-py3-none-any.whl:
Publisher:
release.yml on ruslan-rv-ua/easy_file
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
easy_file-0.4.1-py3-none-any.whl -
Subject digest:
74ffc15f1d8771c282c0619be986d0783ff867b70a52bb015bb0d524d4a71b96 - Sigstore transparency entry: 779443558
- Sigstore integration time:
-
Permalink:
ruslan-rv-ua/easy_file@b4cfdfaf522877df34832250d9152e7f5e8f1d49 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ruslan-rv-ua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b4cfdfaf522877df34832250d9152e7f5e8f1d49 -
Trigger Event:
workflow_dispatch
-
Statement type: