# pathlint
[](https://pypi.org/project/pathlint/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/astral-sh/ruff)
[](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[](https://pypi.org/project/pathlint/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/astral-sh/ruff)\n[](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"
}