pathlint


Namepathlint JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryFast linter to detect os.path usage and encourage pathlib adoption
upload_time2025-09-07 19:04:06
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords ast code-quality linter os.path pathlib python-linter static-analysis
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pathlint

[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)](https://pypi.org/project/pathlint/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
[![PyPI version](https://badge.fury.io/py/pathlint.svg)](https://pypi.org/project/pathlint/)

> Fast linter to detect os.path usage and encourage pathlib adoption

## Why pathlint?

Still using `os.path` in 2025+? `pathlint` is a fast, comprehensive linter that detects **all** `os.path` usage patterns in your Python codebase and encourages migration to the modern `pathlib` module.

### Key Features

- **Comprehensive Detection**: Catches import statements, aliased imports, function calls, and even type annotations
- **Performance Optimized**: 3x faster than traditional AST-based linters with early termination
- **Smart Exclusions**: Automatically skips `venv`, `__pycache__`, `node_modules`, and other common directories
- **Professional Output**: Clean, informative output with optional statistics
- **Auto-fix Support**: Experimental auto-fixer to migrate code automatically

## Installation

```bash
pip install pathlint
```

## Usage

### Basic Linting

```bash
# Lint files or directories
pathlint myfile.py
pathlint src/
pathlint .

# Multiple paths
pathlint src/ tests/ scripts/
```

### Advanced Options

```bash
# Show statistics about worst offenders
pathlint . --stats

# Aggressive mode (for fun)
pathlint . --aggressive

# Auto-fix mode (experimental)
pathlint --dry-run src/  # Preview changes
pathlint --fix src/      # Apply fixes
```

## What It Detects

pathlint catches ALL these patterns:

```python
# Import patterns
import os.path
import os.path as ospath
from os import path
from os import path as p
from os.path import join, exists

# Direct usage
os.path.exists('file.txt')
os.path.join('dir', 'file')
path.dirname(__file__)  # After 'from os import path'

# Type annotations (missed by most linters!)
def process(f: os.path.PathLike):
    pass

def get_path() -> os.path.PathLike:
    return 'test'
```

## Output Format

### Clean Files
```
✓ 42 files checked - no os.path usage found!
```

### Files with Issues
```
/path/to/file.py
  L   1: import os.path
  L  23: x = os.path.join('a', 'b')
  L  45: def process(f: os.path.PathLike):

────────────────────────────────────────
Files checked:     42
Files with issues: 3
Total violations:  7

✗ Found os.path usage. Migrate to pathlib.
```

### With Statistics (`--stats`)
```
Worst offenders:
   12 - legacy_utils.py
    5 - old_config.py
    2 - setup.py
```

## Exit Codes

- `0` - No os.path usage found
- `1` - os.path usage detected
- `2` - Error (no files found, invalid paths, etc.)

## Performance

Optimized for speed with:
- Early termination for files without 'os' or 'path' strings
- Smart directory traversal with automatic exclusions
- Single-pass AST visitor
- Automatic deduplication of findings

Benchmarks on real codebases:
```
100 files: 0.31s (vs 0.84s traditional)
500 files: 1.1s (vs 4.2s traditional)
```

## Auto-fix (Experimental)

Pathlint can automatically migrate common os.path patterns:

```bash
# Preview changes without modifying files
pathlint --dry-run myfile.py

# Apply fixes (modifies files!)
pathlint --fix myfile.py

# Fix entire directory
pathlint --fix src/
```

Supports migration of:
- Import statements
- Common function calls (`exists`, `join`, `dirname`, etc.)
- Path attributes
- Automatic `pathlib` import addition

**⚠️ Warning**: Always review auto-fixed code and test thoroughly!

## Development

```bash
# Install with dev dependencies
pip install -e .[dev,test]

# Run tests
pytest

# Format code
ruff format .

# Check linting
ruff check --fix .
```

## Why Pathlib?

`pathlib` provides:
- Object-oriented interface
- Operator overloading (`/` for joining paths)
- Cross-platform compatibility
- Rich path manipulation methods
- Type safety with `Path` objects

```python
# Old way (os.path)
import os.path
filepath = os.path.join(os.path.dirname(__file__), 'data', 'config.json')
if os.path.exists(filepath):
    abs_path = os.path.abspath(filepath)

# Modern way (pathlib)
from pathlib import Path
filepath = Path(__file__).parent / 'data' / 'config.json'
if filepath.exists():
    abs_path = filepath.resolve()
```

**Note**: While pathlib is recommended for most use cases, there are rare scenarios where `os.path` might offer better performance[^1].

[^1]: In extremely performance-critical code paths dealing with millions of file operations, `os.path` string operations can be marginally faster than Path object instantiation. However, these edge cases are rare and should only be considered after profiling confirms a bottleneck.

## License

MIT License - see LICENSE.txt

## Contributing

Contributions welcome! Please ensure:
1. Tests pass: `pytest`
2. Code is formatted: `ruff format .`
3. No linting errors: `ruff check .`

---

**Remember**: Friends don't let friends use `os.path` in 2025+!

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pathlint",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "ast, code-quality, linter, os.path, pathlib, python-linter, static-analysis",
    "author": null,
    "author_email": "Peter Szemraj <peterszemraj+dev@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/1c/33/2b23d635e2c6cd094c5278da107ce4ca299a118efc094036fed4eb309a69/pathlint-0.1.0.tar.gz",
    "platform": null,
    "description": "# pathlint\n\n[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)](https://pypi.org/project/pathlint/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)\n[![PyPI version](https://badge.fury.io/py/pathlint.svg)](https://pypi.org/project/pathlint/)\n\n> Fast linter to detect os.path usage and encourage pathlib adoption\n\n## Why pathlint?\n\nStill using `os.path` in 2025+? `pathlint` is a fast, comprehensive linter that detects **all** `os.path` usage patterns in your Python codebase and encourages migration to the modern `pathlib` module.\n\n### Key Features\n\n- **Comprehensive Detection**: Catches import statements, aliased imports, function calls, and even type annotations\n- **Performance Optimized**: 3x faster than traditional AST-based linters with early termination\n- **Smart Exclusions**: Automatically skips `venv`, `__pycache__`, `node_modules`, and other common directories\n- **Professional Output**: Clean, informative output with optional statistics\n- **Auto-fix Support**: Experimental auto-fixer to migrate code automatically\n\n## Installation\n\n```bash\npip install pathlint\n```\n\n## Usage\n\n### Basic Linting\n\n```bash\n# Lint files or directories\npathlint myfile.py\npathlint src/\npathlint .\n\n# Multiple paths\npathlint src/ tests/ scripts/\n```\n\n### Advanced Options\n\n```bash\n# Show statistics about worst offenders\npathlint . --stats\n\n# Aggressive mode (for fun)\npathlint . --aggressive\n\n# Auto-fix mode (experimental)\npathlint --dry-run src/  # Preview changes\npathlint --fix src/      # Apply fixes\n```\n\n## What It Detects\n\npathlint catches ALL these patterns:\n\n```python\n# Import patterns\nimport os.path\nimport os.path as ospath\nfrom os import path\nfrom os import path as p\nfrom os.path import join, exists\n\n# Direct usage\nos.path.exists('file.txt')\nos.path.join('dir', 'file')\npath.dirname(__file__)  # After 'from os import path'\n\n# Type annotations (missed by most linters!)\ndef process(f: os.path.PathLike):\n    pass\n\ndef get_path() -> os.path.PathLike:\n    return 'test'\n```\n\n## Output Format\n\n### Clean Files\n```\n\u2713 42 files checked - no os.path usage found!\n```\n\n### Files with Issues\n```\n/path/to/file.py\n  L   1: import os.path\n  L  23: x = os.path.join('a', 'b')\n  L  45: def process(f: os.path.PathLike):\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nFiles checked:     42\nFiles with issues: 3\nTotal violations:  7\n\n\u2717 Found os.path usage. Migrate to pathlib.\n```\n\n### With Statistics (`--stats`)\n```\nWorst offenders:\n   12 - legacy_utils.py\n    5 - old_config.py\n    2 - setup.py\n```\n\n## Exit Codes\n\n- `0` - No os.path usage found\n- `1` - os.path usage detected\n- `2` - Error (no files found, invalid paths, etc.)\n\n## Performance\n\nOptimized for speed with:\n- Early termination for files without 'os' or 'path' strings\n- Smart directory traversal with automatic exclusions\n- Single-pass AST visitor\n- Automatic deduplication of findings\n\nBenchmarks on real codebases:\n```\n100 files: 0.31s (vs 0.84s traditional)\n500 files: 1.1s (vs 4.2s traditional)\n```\n\n## Auto-fix (Experimental)\n\nPathlint can automatically migrate common os.path patterns:\n\n```bash\n# Preview changes without modifying files\npathlint --dry-run myfile.py\n\n# Apply fixes (modifies files!)\npathlint --fix myfile.py\n\n# Fix entire directory\npathlint --fix src/\n```\n\nSupports migration of:\n- Import statements\n- Common function calls (`exists`, `join`, `dirname`, etc.)\n- Path attributes\n- Automatic `pathlib` import addition\n\n**\u26a0\ufe0f Warning**: Always review auto-fixed code and test thoroughly!\n\n## Development\n\n```bash\n# Install with dev dependencies\npip install -e .[dev,test]\n\n# Run tests\npytest\n\n# Format code\nruff format .\n\n# Check linting\nruff check --fix .\n```\n\n## Why Pathlib?\n\n`pathlib` provides:\n- Object-oriented interface\n- Operator overloading (`/` for joining paths)\n- Cross-platform compatibility\n- Rich path manipulation methods\n- Type safety with `Path` objects\n\n```python\n# Old way (os.path)\nimport os.path\nfilepath = os.path.join(os.path.dirname(__file__), 'data', 'config.json')\nif os.path.exists(filepath):\n    abs_path = os.path.abspath(filepath)\n\n# Modern way (pathlib)\nfrom pathlib import Path\nfilepath = Path(__file__).parent / 'data' / 'config.json'\nif filepath.exists():\n    abs_path = filepath.resolve()\n```\n\n**Note**: While pathlib is recommended for most use cases, there are rare scenarios where `os.path` might offer better performance[^1].\n\n[^1]: In extremely performance-critical code paths dealing with millions of file operations, `os.path` string operations can be marginally faster than Path object instantiation. However, these edge cases are rare and should only be considered after profiling confirms a bottleneck.\n\n## License\n\nMIT License - see LICENSE.txt\n\n## Contributing\n\nContributions welcome! Please ensure:\n1. Tests pass: `pytest`\n2. Code is formatted: `ruff format .`\n3. No linting errors: `ruff check .`\n\n---\n\n**Remember**: Friends don't let friends use `os.path` in 2025+!\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Fast linter to detect os.path usage and encourage pathlib adoption",
    "version": "0.1.0",
    "project_urls": {
        "Changelog": "https://github.com/pszemraj/pathlint/releases",
        "Documentation": "https://github.com/pszemraj/pathlint#readme",
        "Issues": "https://github.com/pszemraj/pathlint/issues",
        "Repository": "https://github.com/pszemraj/pathlint"
    },
    "split_keywords": [
        "ast",
        " code-quality",
        " linter",
        " os.path",
        " pathlib",
        " python-linter",
        " static-analysis"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "60a2f406b87d84bb084a912dc040c6fae47a1f9c050b5fced39723967be21f86",
                "md5": "0484c4e250a324e39e4250836b0637e8",
                "sha256": "7401d07704d7ac88c3254b6c87b8e7d689057e7f5f835e061925ffa7be740c07"
            },
            "downloads": -1,
            "filename": "pathlint-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "0484c4e250a324e39e4250836b0637e8",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 10556,
            "upload_time": "2025-09-07T19:04:05",
            "upload_time_iso_8601": "2025-09-07T19:04:05.248111Z",
            "url": "https://files.pythonhosted.org/packages/60/a2/f406b87d84bb084a912dc040c6fae47a1f9c050b5fced39723967be21f86/pathlint-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1c332b23d635e2c6cd094c5278da107ce4ca299a118efc094036fed4eb309a69",
                "md5": "bc40382efbd26caebb429722eaafc503",
                "sha256": "1b5231f1622ce8c82f491a920163688a090c019e209ade662f53938353ac97c9"
            },
            "downloads": -1,
            "filename": "pathlint-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "bc40382efbd26caebb429722eaafc503",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 11033,
            "upload_time": "2025-09-07T19:04:06",
            "upload_time_iso_8601": "2025-09-07T19:04:06.493822Z",
            "url": "https://files.pythonhosted.org/packages/1c/33/2b23d635e2c6cd094c5278da107ce4ca299a118efc094036fed4eb309a69/pathlint-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-07 19:04:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "pszemraj",
    "github_project": "pathlint",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pathlint"
}
        
Elapsed time: 2.48600s