Generate unused file and directory paths by auto-incrementing numeric suffixes. Similar to how browsers handle duplicate downloads.
Project description
unused-path
Generate unused file and directory paths by auto-incrementing numeric suffixes. Similar to how browsers handle duplicate downloads.
Features
- Automatic numbering: Appends numeric suffixes like "file (1).txt", "file (2).txt" to avoid conflicts
- Intelligent sequencing: Continues from existing numbered files/directories
- Custom formatting: Support for custom formatter functions
- Zero dependencies: Lightweight with no external dependencies
- Thread-safe — Optional atomic file/directory creation for concurrent environments
- Fully typed — Complete type hints for excellent IDE autocomplete
- Battle-tested — Handles edge cases, gaps in sequences, and special characters
Installation
pip install unused-path
Why unused-path?
When working with file operations, you often need to avoid overwriting existing files:
- ❌ Downloading files that might already exist
- ❌ Creating backup directories with the same name
- ❌ Exporting data to files that may already be present
- ❌ Generating temporary files without conflicts
unused-path handles this automatically, similar to how browsers handle duplicate downloads.
Usage Examples
Basic Usage
from unused_path import unused_filename, unused_directory
# Generate unused filename
path = unused_filename("document.pdf")
print(path) # 'document.pdf' (if available)
# If file exists, automatically increments
path = unused_filename("document.pdf")
print(path) # 'document (1).pdf'
# Same for directories
dir_path = unused_directory("backup")
print(dir_path) # 'backup' or 'backup (1)' if exists
Thread-Safe Creation
Perfect for multi-threaded applications or concurrent workers:
# Atomically creates the file — no race conditions!
path = unused_filename("download.zip", create=True)
with open(path, 'wb') as f:
f.write(data)
# Same for directories
export_dir = unused_directory("exports", create=True)
Custom Formatting
# Version numbers with zero-padding
formatter = lambda base, ext, n: f"{base}_v{n:03d}{ext}"
path = unused_filename("log.txt", formatter=formatter)
# → 'log_v001.txt', 'log_v002.txt', ...
# Underscore style
formatter = lambda base, ext, n: f"{base}_{n}{ext}"
path = unused_filename("data.csv", formatter=formatter)
# → 'data_1.csv', 'data_2.csv', ...
# Similar formatting for directories
formatter = lambda base, n: f"{base}_v{n}"
dir_path = unused_directory("backup", formatter=formatter)
# → 'backup_1', 'backup_2', ...
API Reference
| Function | Description |
|---|---|
unused_filename(path, *, formatter=None, max_tries=10000, create=False) |
Generate unused filename by appending numeric suffix if needed |
unused_directory(path, *, formatter=None, max_tries=10000, create=False) |
Generate unused directory name by appending numeric suffix if needed |
Parameters
path: Desired directory path (relative or absolute)formatter: Optional custom formatter functionType Signature File (base: str, ext: str, n: int) -> strDirectory (base: str, n: int) -> strmax_tries: Maximum attempts to find unused name (default: 10,000)create: If True, atomically creates the directory to prevent race conditions. Useful in multi-threaded/multi-process environments where multiple workers might generate directories simultaneously. The created directory will be empty and owned by the calling process.
Returns
- Unused path (str) - preserves the absolute or relative format of the input
Raises
RuntimeError: If no unused path is found within max_tries attemptsOSError: If file/directory creation fails when create=True (e.g., permission denied)
Real-World Examples
Download Manager
import requests
from unused_path import unused_filename
def download_file(url):
filename = url.split('/')[-1]
path = unused_filename(filename, create=True)
response = requests.get(url)
with open(path, 'wb') as f:
f.write(response.content)
return path
Backup System
from datetime import datetime
from unused_path import unused_directory
def create_backup():
timestamp = datetime.now().strftime("%Y%m%d")
backup_dir = unused_directory(f"backup_{timestamp}", create=True)
# ... perform backup ...
return backup_dir
Data Export
from unused_path import unused_filename
def export_to_csv(data, base_name="export"):
path = unused_filename(f"{base_name}.csv", create=True)
data.to_csv(path, index=False)
print(f"✓ Exported to {path}")
return path
Temporary Files
from unused_path import unused_filename
import tempfile
import os
def create_temp_file(prefix="temp"):
temp_dir = tempfile.gettempdir()
path = os.path.join(temp_dir, f"{prefix}.txt")
return unused_filename(path, create=True)
Advanced Patterns
Custom Formatter with Timestamp
from datetime import datetime
def timestamp_formatter(base, ext, n):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
return f"{base}_{timestamp}_{n}{ext}"
path = unused_filename("log.txt", formatter=timestamp_formatter)
# → 'log_20240115_143052_1.txt'
Limiting Maximum Tries
try:
path = unused_filename("file.txt", max_tries=100)
except RuntimeError:
print("Too many files with the same name!")
Changelog
See CHANGELOG.md for a detailed list of changes and version history.
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Support
If you find this library helpful:
- ⭐ Star the repository
- 🐛 Report issues
- 🔀 Submit pull requests
- 💝 Sponsor on GitHub
License
MIT © Y. Siva Sai Krishna - see LICENSE file for details.
Author's GitHub • Author's LinkedIn • Report Issues • Package on PyPI
Project details
Release history Release notifications | RSS feed
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 unused_path-1.1.0.tar.gz.
File metadata
- Download URL: unused_path-1.1.0.tar.gz
- Upload date:
- Size: 23.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b8d38ee9e7a683e9cb6138b1fb59164ce4a63fe53c77df74e439902b9beb291
|
|
| MD5 |
d3992dd64bbe8bbfccbd8e4c093e8cd1
|
|
| BLAKE2b-256 |
601ccfd3862d1b75fd5444a594b72f168a2d9c75688a36f0f3e2ddbf85231c5e
|
File details
Details for the file unused_path-1.1.0-py3-none-any.whl.
File metadata
- Download URL: unused_path-1.1.0-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
77bc5509b97a5a3d3199d573d2635f341d973d39b3adc16942885522c0714c6d
|
|
| MD5 |
bb120a2514713c90b103bc7e5ae0aa08
|
|
| BLAKE2b-256 |
97d3c5d2492da593f2d6335cd097e18b2582d7f43fc0f545df67c1c2098f981a
|