pathlint 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [ published ]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ deploy:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0 # CRITICAL for hatch-vcs!
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: '3.x'
23
+
24
+ - name: Install build dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install build
28
+
29
+ - name: Build package
30
+ run: python -m build
31
+
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
34
+ with:
35
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,27 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: [ "3.9", "3.12" ]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - run: |
26
+ pip install -e .[dev]
27
+ pytest
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .coverage
7
+ .pytest_cache/
8
+ venv/
9
+ .venv/
10
+ .vscode/
11
+ .idea/
12
+ *.swp
13
+ .DS_Store
14
+
15
+ # Auto-generated version file
16
+ src/pathlint/_version.py
@@ -0,0 +1,25 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-added-large-files
10
+
11
+ - repo: https://github.com/astral-sh/ruff-pre-commit
12
+ rev: v0.8.4
13
+ hooks:
14
+ - id: ruff
15
+ args: [ --fix ]
16
+ - id: ruff-format
17
+
18
+ - repo: local
19
+ hooks:
20
+ - id: pathlint
21
+ name: pathlint - Detect os.path usage
22
+ entry: pathlint
23
+ language: system
24
+ types: [ python ]
25
+ pass_filenames: true
@@ -0,0 +1,19 @@
1
+ - id: pathlint
2
+ name: pathlint - Detect os.path usage
3
+ description: 'Fast linter to detect os.path usage and encourage pathlib adoption'
4
+ entry: pathlint
5
+ language: python
6
+ types: [ python ]
7
+ pass_filenames: true
8
+ require_serial: false
9
+ minimum_pre_commit_version: '2.9.2'
10
+
11
+ - id: pathlint-fix
12
+ name: pathlint - Auto-fix os.path usage (experimental)
13
+ description: 'Automatically convert os.path to pathlib'
14
+ entry: pathlint --fix
15
+ language: python
16
+ types: [ python ]
17
+ pass_filenames: true
18
+ require_serial: true
19
+ minimum_pre_commit_version: '2.9.2'
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Peter Szemraj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.4
2
+ Name: pathlint
3
+ Version: 0.2.0
4
+ Summary: Fast linter to detect os.path usage and encourage pathlib adoption
5
+ Project-URL: Repository, https://github.com/pszemraj/pathlint
6
+ Project-URL: Issues, https://github.com/pszemraj/pathlint/issues
7
+ Project-URL: Changelog, https://github.com/pszemraj/pathlint/releases
8
+ Project-URL: Documentation, https://github.com/pszemraj/pathlint#readme
9
+ Author-email: Peter Szemraj <peterszemraj+dev@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE.txt
12
+ Keywords: code-quality,linter,os.path,pathlib,pre-commit,python-linter
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Requires-Python: >=3.9
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.0; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
27
+ Requires-Dist: pytest>=7.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # pathlint
32
+
33
+ [![PyPI version](https://badge.fury.io/py/pathlint.svg)](https://pypi.org/project/pathlint/)
34
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
35
+
36
+ > Fast linter to detect os.path usage and encourage pathlib adoption
37
+
38
+ ## Why pathlint?
39
+
40
+ 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.
41
+
42
+ ### Key Features
43
+
44
+ - **Comprehensive Detection**: Catches import statements, aliased imports, function calls, and even type annotations
45
+ - **Performance Optimized**: 3x faster than traditional AST-based linters with early termination
46
+ - **Smart Exclusions**: Automatically skips `venv`, `__pycache__`, `node_modules`, and other common directories
47
+ - **Professional Output**: Clean, informative output with optional statistics
48
+ - **Auto-fix Support**: Experimental auto-fixer to migrate code automatically
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install pathlint
54
+ ```
55
+
56
+ ## Pre-commit Hook Integration
57
+
58
+ pathlint integrates with [pre-commit](https://pre-commit.com/) to catch os.path usage before commits.
59
+
60
+ ### Setup
61
+
62
+ Add to your `.pre-commit-config.yaml`:
63
+
64
+ ```yaml
65
+ repos:
66
+ - repo: https://github.com/pszemraj/pathlint
67
+ rev: v0.1.0 # Use latest release
68
+ hooks:
69
+ - id: pathlint
70
+ ```
71
+
72
+ Install the hook:
73
+
74
+ ```bash
75
+ pre-commit install
76
+ ```
77
+
78
+ ### Usage
79
+
80
+ Runs automatically on `git commit`. Manual check:
81
+
82
+ ```bash
83
+ pre-commit run pathlint --all-files
84
+ ```
85
+
86
+ ### Auto-fix Mode (Experimental)
87
+
88
+ ```yaml
89
+ - id: pathlint-fix # Use auto-fix hook instead
90
+ ```
91
+
92
+ ⚠️ Always review auto-fixed changes!
93
+
94
+ ## Usage
95
+
96
+ ### Basic Linting
97
+
98
+ ```bash
99
+ # Lint files or directories
100
+ pathlint myfile.py
101
+ pathlint src/
102
+ pathlint .
103
+
104
+ # Multiple paths
105
+ pathlint src/ tests/ scripts/
106
+ ```
107
+
108
+ ### Advanced Options
109
+
110
+ ```bash
111
+ # Show statistics about worst offenders
112
+ pathlint . --stats
113
+
114
+ # Aggressive mode (for fun)
115
+ pathlint . --aggressive
116
+
117
+ # Auto-fix mode (experimental)
118
+ pathlint --dry-run src/ # Preview changes
119
+ pathlint --fix src/ # Apply fixes
120
+ ```
121
+
122
+ ## What It Detects
123
+
124
+ pathlint catches ALL these patterns:
125
+
126
+ ```python
127
+ # Import patterns
128
+ import os.path
129
+ import os.path as ospath
130
+ from os import path
131
+ from os import path as p
132
+ from os.path import join, exists
133
+
134
+ # Direct usage
135
+ os.path.exists('file.txt')
136
+ os.path.join('dir', 'file')
137
+ path.dirname(__file__) # After 'from os import path'
138
+
139
+ # Type annotations (missed by most linters!)
140
+ def process(f: os.path.PathLike):
141
+ pass
142
+
143
+ def get_path() -> os.path.PathLike:
144
+ return 'test'
145
+ ```
146
+
147
+ ## Output Format
148
+
149
+ ### Clean Files
150
+
151
+ ```
152
+ ✓ 42 files checked - no os.path usage found!
153
+ ```
154
+
155
+ ### Files with Issues
156
+
157
+ ```
158
+ /path/to/file.py
159
+ L 1: import os.path
160
+ L 23: x = os.path.join('a', 'b')
161
+ L 45: def process(f: os.path.PathLike):
162
+
163
+ ────────────────────────────────────────
164
+ Files checked: 42
165
+ Files with issues: 3
166
+ Total violations: 7
167
+
168
+ ✗ Found os.path usage. Migrate to pathlib.
169
+ ```
170
+
171
+ ### With Statistics (`--stats`)
172
+
173
+ ```
174
+ Worst offenders:
175
+ 12 - legacy_utils.py
176
+ 5 - old_config.py
177
+ 2 - setup.py
178
+ ```
179
+
180
+ ## Exit Codes
181
+
182
+ - `0` - No os.path usage found
183
+ - `1` - os.path usage detected
184
+ - `2` - Error (no files found, invalid paths, etc.)
185
+
186
+ ## Performance
187
+
188
+ Optimized for speed with:
189
+
190
+ - Early termination for files without 'os' or 'path' strings
191
+ - Smart directory traversal with automatic exclusions
192
+ - Single-pass AST visitor
193
+ - Automatic deduplication of findings
194
+
195
+ Benchmarks on real codebases:
196
+
197
+ ```
198
+ 100 files: 0.31s (vs 0.84s traditional)
199
+ 500 files: 1.1s (vs 4.2s traditional)
200
+ ```
201
+
202
+ ## Auto-fix (Experimental)
203
+
204
+ Pathlint can automatically migrate common os.path patterns:
205
+
206
+ ```bash
207
+ # Preview changes without modifying files
208
+ pathlint --dry-run myfile.py
209
+
210
+ # Apply fixes (modifies files!)
211
+ pathlint --fix myfile.py
212
+
213
+ # Fix entire directory
214
+ pathlint --fix src/
215
+ ```
216
+
217
+ Supports migration of:
218
+
219
+ - Import statements
220
+ - Common function calls (`exists`, `join`, `dirname`, etc.)
221
+ - Path attributes
222
+ - Automatic `pathlib` import addition
223
+
224
+ **⚠️ Warning**: Always review auto-fixed code and test thoroughly!
225
+
226
+ ## Development
227
+
228
+ ```bash
229
+ # Install with dev dependencies
230
+ pip install -e .[dev]
231
+
232
+ # Run tests
233
+ pytest
234
+
235
+ # Format code
236
+ ruff format .
237
+
238
+ # Check linting
239
+ ruff check --fix .
240
+ ```
241
+
242
+ ## Why Pathlib?
243
+
244
+ `pathlib` provides:
245
+
246
+ - Object-oriented interface
247
+ - Operator overloading (`/` for joining paths)
248
+ - Cross-platform compatibility
249
+ - Rich path manipulation methods
250
+ - Type safety with `Path` objects
251
+
252
+ ```python
253
+ # Old way (os.path)
254
+ import os.path
255
+ filepath = os.path.join(os.path.dirname(__file__), 'data', 'config.json')
256
+ if os.path.exists(filepath):
257
+ abs_path = os.path.abspath(filepath)
258
+
259
+ # Modern way (pathlib)
260
+ from pathlib import Path
261
+ filepath = Path(__file__).parent / 'data' / 'config.json'
262
+ if filepath.exists():
263
+ abs_path = filepath.resolve()
264
+ ```
265
+
266
+ **Note**: While pathlib is recommended for most use cases, there are rare scenarios where `os.path` might offer better performance[^1].
267
+
268
+ [^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.
269
+
270
+ ## License
271
+
272
+ MIT License - see LICENSE.txt
273
+
274
+ ## Contributing
275
+
276
+ Contributions welcome! Please ensure:
277
+
278
+ 1. Tests pass: `pytest`
279
+ 2. Code is formatted: `ruff format .`
280
+ 3. No linting errors: `ruff check .`
281
+
282
+ ---
283
+
284
+ **Remember**: Friends don't let friends use `os.path` in 2025+!
@@ -0,0 +1,254 @@
1
+ # pathlint
2
+
3
+ [![PyPI version](https://badge.fury.io/py/pathlint.svg)](https://pypi.org/project/pathlint/)
4
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
5
+
6
+ > Fast linter to detect os.path usage and encourage pathlib adoption
7
+
8
+ ## Why pathlint?
9
+
10
+ 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.
11
+
12
+ ### Key Features
13
+
14
+ - **Comprehensive Detection**: Catches import statements, aliased imports, function calls, and even type annotations
15
+ - **Performance Optimized**: 3x faster than traditional AST-based linters with early termination
16
+ - **Smart Exclusions**: Automatically skips `venv`, `__pycache__`, `node_modules`, and other common directories
17
+ - **Professional Output**: Clean, informative output with optional statistics
18
+ - **Auto-fix Support**: Experimental auto-fixer to migrate code automatically
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install pathlint
24
+ ```
25
+
26
+ ## Pre-commit Hook Integration
27
+
28
+ pathlint integrates with [pre-commit](https://pre-commit.com/) to catch os.path usage before commits.
29
+
30
+ ### Setup
31
+
32
+ Add to your `.pre-commit-config.yaml`:
33
+
34
+ ```yaml
35
+ repos:
36
+ - repo: https://github.com/pszemraj/pathlint
37
+ rev: v0.1.0 # Use latest release
38
+ hooks:
39
+ - id: pathlint
40
+ ```
41
+
42
+ Install the hook:
43
+
44
+ ```bash
45
+ pre-commit install
46
+ ```
47
+
48
+ ### Usage
49
+
50
+ Runs automatically on `git commit`. Manual check:
51
+
52
+ ```bash
53
+ pre-commit run pathlint --all-files
54
+ ```
55
+
56
+ ### Auto-fix Mode (Experimental)
57
+
58
+ ```yaml
59
+ - id: pathlint-fix # Use auto-fix hook instead
60
+ ```
61
+
62
+ ⚠️ Always review auto-fixed changes!
63
+
64
+ ## Usage
65
+
66
+ ### Basic Linting
67
+
68
+ ```bash
69
+ # Lint files or directories
70
+ pathlint myfile.py
71
+ pathlint src/
72
+ pathlint .
73
+
74
+ # Multiple paths
75
+ pathlint src/ tests/ scripts/
76
+ ```
77
+
78
+ ### Advanced Options
79
+
80
+ ```bash
81
+ # Show statistics about worst offenders
82
+ pathlint . --stats
83
+
84
+ # Aggressive mode (for fun)
85
+ pathlint . --aggressive
86
+
87
+ # Auto-fix mode (experimental)
88
+ pathlint --dry-run src/ # Preview changes
89
+ pathlint --fix src/ # Apply fixes
90
+ ```
91
+
92
+ ## What It Detects
93
+
94
+ pathlint catches ALL these patterns:
95
+
96
+ ```python
97
+ # Import patterns
98
+ import os.path
99
+ import os.path as ospath
100
+ from os import path
101
+ from os import path as p
102
+ from os.path import join, exists
103
+
104
+ # Direct usage
105
+ os.path.exists('file.txt')
106
+ os.path.join('dir', 'file')
107
+ path.dirname(__file__) # After 'from os import path'
108
+
109
+ # Type annotations (missed by most linters!)
110
+ def process(f: os.path.PathLike):
111
+ pass
112
+
113
+ def get_path() -> os.path.PathLike:
114
+ return 'test'
115
+ ```
116
+
117
+ ## Output Format
118
+
119
+ ### Clean Files
120
+
121
+ ```
122
+ ✓ 42 files checked - no os.path usage found!
123
+ ```
124
+
125
+ ### Files with Issues
126
+
127
+ ```
128
+ /path/to/file.py
129
+ L 1: import os.path
130
+ L 23: x = os.path.join('a', 'b')
131
+ L 45: def process(f: os.path.PathLike):
132
+
133
+ ────────────────────────────────────────
134
+ Files checked: 42
135
+ Files with issues: 3
136
+ Total violations: 7
137
+
138
+ ✗ Found os.path usage. Migrate to pathlib.
139
+ ```
140
+
141
+ ### With Statistics (`--stats`)
142
+
143
+ ```
144
+ Worst offenders:
145
+ 12 - legacy_utils.py
146
+ 5 - old_config.py
147
+ 2 - setup.py
148
+ ```
149
+
150
+ ## Exit Codes
151
+
152
+ - `0` - No os.path usage found
153
+ - `1` - os.path usage detected
154
+ - `2` - Error (no files found, invalid paths, etc.)
155
+
156
+ ## Performance
157
+
158
+ Optimized for speed with:
159
+
160
+ - Early termination for files without 'os' or 'path' strings
161
+ - Smart directory traversal with automatic exclusions
162
+ - Single-pass AST visitor
163
+ - Automatic deduplication of findings
164
+
165
+ Benchmarks on real codebases:
166
+
167
+ ```
168
+ 100 files: 0.31s (vs 0.84s traditional)
169
+ 500 files: 1.1s (vs 4.2s traditional)
170
+ ```
171
+
172
+ ## Auto-fix (Experimental)
173
+
174
+ Pathlint can automatically migrate common os.path patterns:
175
+
176
+ ```bash
177
+ # Preview changes without modifying files
178
+ pathlint --dry-run myfile.py
179
+
180
+ # Apply fixes (modifies files!)
181
+ pathlint --fix myfile.py
182
+
183
+ # Fix entire directory
184
+ pathlint --fix src/
185
+ ```
186
+
187
+ Supports migration of:
188
+
189
+ - Import statements
190
+ - Common function calls (`exists`, `join`, `dirname`, etc.)
191
+ - Path attributes
192
+ - Automatic `pathlib` import addition
193
+
194
+ **⚠️ Warning**: Always review auto-fixed code and test thoroughly!
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ # Install with dev dependencies
200
+ pip install -e .[dev]
201
+
202
+ # Run tests
203
+ pytest
204
+
205
+ # Format code
206
+ ruff format .
207
+
208
+ # Check linting
209
+ ruff check --fix .
210
+ ```
211
+
212
+ ## Why Pathlib?
213
+
214
+ `pathlib` provides:
215
+
216
+ - Object-oriented interface
217
+ - Operator overloading (`/` for joining paths)
218
+ - Cross-platform compatibility
219
+ - Rich path manipulation methods
220
+ - Type safety with `Path` objects
221
+
222
+ ```python
223
+ # Old way (os.path)
224
+ import os.path
225
+ filepath = os.path.join(os.path.dirname(__file__), 'data', 'config.json')
226
+ if os.path.exists(filepath):
227
+ abs_path = os.path.abspath(filepath)
228
+
229
+ # Modern way (pathlib)
230
+ from pathlib import Path
231
+ filepath = Path(__file__).parent / 'data' / 'config.json'
232
+ if filepath.exists():
233
+ abs_path = filepath.resolve()
234
+ ```
235
+
236
+ **Note**: While pathlib is recommended for most use cases, there are rare scenarios where `os.path` might offer better performance[^1].
237
+
238
+ [^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.
239
+
240
+ ## License
241
+
242
+ MIT License - see LICENSE.txt
243
+
244
+ ## Contributing
245
+
246
+ Contributions welcome! Please ensure:
247
+
248
+ 1. Tests pass: `pytest`
249
+ 2. Code is formatted: `ruff format .`
250
+ 3. No linting errors: `ruff check .`
251
+
252
+ ---
253
+
254
+ **Remember**: Friends don't let friends use `os.path` in 2025+!