Skip to main content

Generate unused file and directory paths by auto-incrementing numeric suffixes. Similar to how browsers handle duplicate downloads.

Project description

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 GitHubAuthor's LinkedInReport IssuesPackage on PyPI

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

unused_path-1.1.0.tar.gz (23.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unused_path-1.1.0-py3-none-any.whl (9.0 kB view details)

Uploaded Python 3

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

Hashes for unused_path-1.1.0.tar.gz
Algorithm Hash digest
SHA256 5b8d38ee9e7a683e9cb6138b1fb59164ce4a63fe53c77df74e439902b9beb291
MD5 d3992dd64bbe8bbfccbd8e4c093e8cd1
BLAKE2b-256 601ccfd3862d1b75fd5444a594b72f168a2d9c75688a36f0f3e2ddbf85231c5e

See more details on using hashes here.

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

Hashes for unused_path-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 77bc5509b97a5a3d3199d573d2635f341d973d39b3adc16942885522c0714c6d
MD5 bb120a2514713c90b103bc7e5ae0aa08
BLAKE2b-256 97d3c5d2492da593f2d6335cd097e18b2582d7f43fc0f545df67c1c2098f981a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page