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
Metadata
Release files for unused-path 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| unused_path-1.1.0.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| unused_path-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.0 kB
Release files / unused_path-1.1.0.tar.gz
| Download URL | unused_path-1.1.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b8d38ee9e7a683e9cb6138b1fb59164ce4a63fe53c77df74e439902b9beb291
|
|
BLAKE2b-256 checksum How to use checksums |
601ccfd3862d1b75fd5444a594b72f168a2d9c75688a36f0f3e2ddbf85231c5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / unused_path-1.1.0-py3-none-any.whl
| Download URL | unused_path-1.1.0-py3-none-any.whl |
|---|---|
| Size | 9.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77bc5509b97a5a3d3199d573d2635f341d973d39b3adc16942885522c0714c6d
|
|
BLAKE2b-256 checksum How to use checksums |
97d3c5d2492da593f2d6335cd097e18b2582d7f43fc0f545df67c1c2098f981a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|