Skip to main content

unused-path

Python Version License Tests PyPI PyPI Downloads

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 function
    Type Signature
    File (base: str, ext: str, n: int) -> str
    Directory (base: str, n: int) -> str
  • max_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 attempts
  • OSError: 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)

Source distribution for unused-path 1.1.0
File Size Uploaded
unused_path-1.1.0.tar.gz 23.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for unused-path 1.1.0
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page