python-naming-linter 0.1.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.
- python_naming_linter-0.1.0/.claude/skills/commit/SKILL.md +24 -0
- python_naming_linter-0.1.0/.claude/skills/release/SKILL.md +14 -0
- python_naming_linter-0.1.0/.github/workflows/ci.yaml +37 -0
- python_naming_linter-0.1.0/.github/workflows/publish.yaml +59 -0
- python_naming_linter-0.1.0/.gitignore +8 -0
- python_naming_linter-0.1.0/.pre-commit-config.yaml +19 -0
- python_naming_linter-0.1.0/.pre-commit-hooks.yaml +6 -0
- python_naming_linter-0.1.0/CLAUDE.md +9 -0
- python_naming_linter-0.1.0/CONTRIBUTING.md +84 -0
- python_naming_linter-0.1.0/LICENSE +21 -0
- python_naming_linter-0.1.0/PKG-INFO +317 -0
- python_naming_linter-0.1.0/README.md +291 -0
- python_naming_linter-0.1.0/pyproject.toml +102 -0
- python_naming_linter-0.1.0/python_naming_linter/__init__.py +0 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/__init__.py +12 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/class_.py +160 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/function.py +137 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/module.py +92 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/package.py +37 -0
- python_naming_linter-0.1.0/python_naming_linter/checkers/variable.py +175 -0
- python_naming_linter-0.1.0/python_naming_linter/cli.py +192 -0
- python_naming_linter-0.1.0/python_naming_linter/config.py +119 -0
- python_naming_linter-0.1.0/python_naming_linter/matcher.py +69 -0
- python_naming_linter-0.1.0/python_naming_linter/reporter.py +16 -0
- python_naming_linter-0.1.0/tests/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_config.yaml +24 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/boards/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/boards/domain/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/boards/domain/models.py +7 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/boards/domain/specs/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/fixtures/sample_project/contexts/boards/domain/specs/filter_spec.py +10 -0
- python_naming_linter-0.1.0/tests/test_checkers/__init__.py +0 -0
- python_naming_linter-0.1.0/tests/test_checkers/test_class.py +104 -0
- python_naming_linter-0.1.0/tests/test_checkers/test_function.py +133 -0
- python_naming_linter-0.1.0/tests/test_checkers/test_module.py +90 -0
- python_naming_linter-0.1.0/tests/test_checkers/test_package.py +42 -0
- python_naming_linter-0.1.0/tests/test_checkers/test_variable.py +142 -0
- python_naming_linter-0.1.0/tests/test_cli.py +129 -0
- python_naming_linter-0.1.0/tests/test_config.py +103 -0
- python_naming_linter-0.1.0/tests/test_matcher.py +55 -0
- python_naming_linter-0.1.0/tests/test_reporter.py +34 -0
- python_naming_linter-0.1.0/uv.lock +394 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit
|
|
3
|
+
description: Create a git commit following the project's commit convention
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a git commit following the project's commit convention defined in [CONTRIBUTING.md](../../../CONTRIBUTING.md).
|
|
7
|
+
|
|
8
|
+
## Steps
|
|
9
|
+
|
|
10
|
+
1. Read `CONTRIBUTING.md` to check the commit convention.
|
|
11
|
+
2. Run `git status` and `git diff` to review all changes.
|
|
12
|
+
3. Draft a commit message following the convention.
|
|
13
|
+
4. Stage only relevant files (do NOT use `git add -A`). Exclude secrets and unrelated files.
|
|
14
|
+
5. Commit using a HEREDOC for proper formatting:
|
|
15
|
+
```bash
|
|
16
|
+
git commit -m "$(cat <<'EOF'
|
|
17
|
+
<commit message here>
|
|
18
|
+
|
|
19
|
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
20
|
+
EOF
|
|
21
|
+
)"
|
|
22
|
+
```
|
|
23
|
+
6. Run `git status` to verify success.
|
|
24
|
+
7. If pre-commit hook fails, fix the issue and create a **new** commit (never amend).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Create a new release by calculating the next version from conventional commits and pushing a git tag
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Create a new release following the project's release process defined in [CONTRIBUTING.md](../../../CONTRIBUTING.md).
|
|
7
|
+
|
|
8
|
+
The release workflow is defined in [.github/workflows/publish.yaml](../../../.github/workflows/publish.yaml).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Read `CONTRIBUTING.md` to check the release process.
|
|
13
|
+
2. Follow the steps described in the Release section.
|
|
14
|
+
3. Ask the user to confirm the version before creating and pushing the tag.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "python_naming_linter/**"
|
|
8
|
+
- "tests/**"
|
|
9
|
+
- "pyproject.toml"
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.13"
|
|
20
|
+
- run: uv pip install --system -e ".[dev]"
|
|
21
|
+
- run: ruff check .
|
|
22
|
+
- run: ruff format --check .
|
|
23
|
+
- run: ty check
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
matrix:
|
|
29
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
|
+
- uses: astral-sh/setup-uv@v4
|
|
33
|
+
- uses: actions/setup-python@v6
|
|
34
|
+
with:
|
|
35
|
+
python-version: ${{ matrix.python-version }}
|
|
36
|
+
- run: uv pip install --system -e ".[dev]"
|
|
37
|
+
- run: pytest -v
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
changelog:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- name: Generate full changelog
|
|
18
|
+
uses: orhun/git-cliff-action@v4
|
|
19
|
+
with:
|
|
20
|
+
config: pyproject.toml
|
|
21
|
+
args: --verbose
|
|
22
|
+
env:
|
|
23
|
+
OUTPUT: CHANGELOG.md
|
|
24
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
25
|
+
- name: Commit CHANGELOG.md
|
|
26
|
+
run: |
|
|
27
|
+
git config user.name "github-actions[bot]"
|
|
28
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
29
|
+
git add CHANGELOG.md
|
|
30
|
+
git commit -m "docs: Update CHANGELOG for ${{ github.ref_name }}" || true
|
|
31
|
+
git push origin HEAD:main
|
|
32
|
+
- name: Generate release notes
|
|
33
|
+
id: release_notes
|
|
34
|
+
uses: orhun/git-cliff-action@v4
|
|
35
|
+
with:
|
|
36
|
+
config: pyproject.toml
|
|
37
|
+
args: --verbose --latest --strip header
|
|
38
|
+
env:
|
|
39
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
40
|
+
- name: Create GitHub Release
|
|
41
|
+
uses: softprops/action-gh-release@v2
|
|
42
|
+
with:
|
|
43
|
+
body: ${{ steps.release_notes.outputs.content }}
|
|
44
|
+
env:
|
|
45
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
46
|
+
|
|
47
|
+
publish:
|
|
48
|
+
needs: changelog
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v6
|
|
54
|
+
- uses: actions/setup-python@v5
|
|
55
|
+
with:
|
|
56
|
+
python-version: "3.13"
|
|
57
|
+
- run: pip install build
|
|
58
|
+
- run: python -m build
|
|
59
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff check
|
|
5
|
+
name: ruff check (lint)
|
|
6
|
+
entry: uv run ruff check --fix
|
|
7
|
+
language: system
|
|
8
|
+
types: [python]
|
|
9
|
+
- id: ruff format
|
|
10
|
+
name: ruff format (format)
|
|
11
|
+
entry: uv run ruff format
|
|
12
|
+
language: system
|
|
13
|
+
types: [python]
|
|
14
|
+
- id: ty check
|
|
15
|
+
name: ty check (type check)
|
|
16
|
+
entry: uv run ty check
|
|
17
|
+
language: system
|
|
18
|
+
pass_filenames: false
|
|
19
|
+
types: [python]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Commit Convention
|
|
4
|
+
|
|
5
|
+
Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/) with [gitmoji](https://gitmoji.dev/) prefix.
|
|
6
|
+
|
|
7
|
+
### Format
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<gitmoji> <type>: <description>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- The first letter after the colon must be **capitalized**.
|
|
14
|
+
- The description must be in **English**.
|
|
15
|
+
|
|
16
|
+
### Types
|
|
17
|
+
|
|
18
|
+
| Gitmoji | Type | Description |
|
|
19
|
+
|---------|------------|--------------------------|
|
|
20
|
+
| ✨ | `feat` | New feature |
|
|
21
|
+
| 🐛 | `fix` | Bug fix |
|
|
22
|
+
| ♻️ | `refactor` | Code refactoring |
|
|
23
|
+
| 📝 | `docs` | Documentation |
|
|
24
|
+
| ✅ | `test` | Adding or updating tests |
|
|
25
|
+
| 🔧 | `chore` | Maintenance tasks |
|
|
26
|
+
| 👷 | `ci` | CI/CD changes |
|
|
27
|
+
| ⚡ | `perf` | Performance improvement |
|
|
28
|
+
|
|
29
|
+
### Examples
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
✨ feat: Add support for relative imports
|
|
33
|
+
🐛 fix: Use exit code 2 for config file not found
|
|
34
|
+
♻️ refactor: Simplify module resolver logic
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Pull Request Convention
|
|
38
|
+
|
|
39
|
+
- PRs are always **squash merged**, so the PR title becomes the final commit message.
|
|
40
|
+
- PR titles must follow the same format as commit messages (`<gitmoji> <type>: <description>`).
|
|
41
|
+
- PR descriptions must be written in **English**.
|
|
42
|
+
|
|
43
|
+
## Pre-commit Hooks
|
|
44
|
+
|
|
45
|
+
This project uses [pre-commit](https://pre-commit.com/) for linting, formatting, and type checking.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Install pre-commit hooks
|
|
49
|
+
pre-commit install
|
|
50
|
+
|
|
51
|
+
# Run manually
|
|
52
|
+
pre-commit run --all-files
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
All commits must pass the pre-commit hooks before being accepted.
|
|
56
|
+
|
|
57
|
+
## Release
|
|
58
|
+
|
|
59
|
+
Releases are automated via GitHub Actions. You only need to create and push a version tag.
|
|
60
|
+
|
|
61
|
+
### Steps
|
|
62
|
+
|
|
63
|
+
1. Calculate the next version based on conventional commits:
|
|
64
|
+
```bash
|
|
65
|
+
uvx git-cliff --bumped-version
|
|
66
|
+
```
|
|
67
|
+
2. Review the commits since the last tag:
|
|
68
|
+
```bash
|
|
69
|
+
git log $(git describe --tags --abbrev=0)..HEAD --oneline
|
|
70
|
+
```
|
|
71
|
+
3. Push the latest commits to `main`:
|
|
72
|
+
```bash
|
|
73
|
+
git push origin main
|
|
74
|
+
```
|
|
75
|
+
4. Create and push the tag:
|
|
76
|
+
```bash
|
|
77
|
+
git tag <version>
|
|
78
|
+
git push origin <version>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The GitHub Actions workflow will then automatically:
|
|
82
|
+
- Generate `CHANGELOG.md` and commit it to `main`
|
|
83
|
+
- Create a GitHub Release with release notes
|
|
84
|
+
- Publish the package to PyPI
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 heumsi
|
|
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,317 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-naming-linter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A naming convention linter for Python projects
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: click>=8.0
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
24
|
+
Requires-Dist: ty>=0.0.26; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# python-naming-linter
|
|
28
|
+
|
|
29
|
+
A naming convention linter for Python projects. Define custom naming rules and enforce them with a single CLI command.
|
|
30
|
+
|
|
31
|
+
## What It Does
|
|
32
|
+
|
|
33
|
+
- Define naming rules for variables, functions, classes, modules, and packages
|
|
34
|
+
- Apply rules to specific modules using pattern matching
|
|
35
|
+
- Integrate into CI or pre-commit to keep your naming conventions consistent
|
|
36
|
+
|
|
37
|
+
For Python developers who want to enforce team-specific naming conventions beyond what PEP 8 and ruff cover.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install python-naming-linter
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or with uv:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv add python-naming-linter
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
Create `.python-naming-linter.yaml` in your project root:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
rules:
|
|
57
|
+
- name: bool-method-prefix
|
|
58
|
+
type: function
|
|
59
|
+
filter: { return_type: bool }
|
|
60
|
+
naming: { prefix: [is_, has_, should_] }
|
|
61
|
+
|
|
62
|
+
- name: exception-naming
|
|
63
|
+
type: class
|
|
64
|
+
filter: { base_class: Exception }
|
|
65
|
+
naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" }
|
|
66
|
+
|
|
67
|
+
apply:
|
|
68
|
+
- name: all
|
|
69
|
+
rules: [bool-method-prefix, exception-naming]
|
|
70
|
+
modules: "**"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pnl check
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Output:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
src/domain/service.py:12
|
|
83
|
+
[bool-method-prefix] validate (expected prefix: is_ | has_ | should_)
|
|
84
|
+
|
|
85
|
+
src/domain/exceptions.py:8
|
|
86
|
+
[exception-naming] FilterError (expected pattern: ^[A-Z][a-zA-Z]+(NotFound|Invalid|...)Error$)
|
|
87
|
+
|
|
88
|
+
Found 2 violation(s).
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Examples
|
|
92
|
+
|
|
93
|
+
### Variable Naming — Match Type Annotation
|
|
94
|
+
|
|
95
|
+
Enforce that variable names match their type annotation in snake_case:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
rules:
|
|
99
|
+
- name: attribute-matches-type
|
|
100
|
+
type: variable
|
|
101
|
+
filter: { target: attribute }
|
|
102
|
+
naming: { source: type_annotation, transform: snake_case }
|
|
103
|
+
|
|
104
|
+
apply:
|
|
105
|
+
- name: domain-layer
|
|
106
|
+
rules: [attribute-matches-type]
|
|
107
|
+
modules: contexts.*.domain
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This catches `repo: SubscriptionRepository` (should be `subscription_repository`).
|
|
111
|
+
|
|
112
|
+
The `{prefix}_{expected}` form is also allowed — `source_object_context: ObjectContext` passes because it ends with `_object_context`.
|
|
113
|
+
|
|
114
|
+
### Module Naming — Match Class Name
|
|
115
|
+
|
|
116
|
+
Enforce that module filenames match the primary class they contain:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
rules:
|
|
120
|
+
- name: domain-module-naming
|
|
121
|
+
type: module
|
|
122
|
+
naming: { source: class_name, transform: snake_case }
|
|
123
|
+
|
|
124
|
+
apply:
|
|
125
|
+
- name: domain-layer
|
|
126
|
+
rules: [domain-module-naming]
|
|
127
|
+
modules: contexts.*.domain
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A file `custom.py` containing `class CustomObject` is a violation — it should be `custom_object.py`.
|
|
131
|
+
|
|
132
|
+
### Combining Rules Per Layer
|
|
133
|
+
|
|
134
|
+
Apply different rules to different parts of your codebase:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
rules:
|
|
138
|
+
- name: attribute-matches-type
|
|
139
|
+
type: variable
|
|
140
|
+
filter: { target: attribute }
|
|
141
|
+
naming: { source: type_annotation, transform: snake_case }
|
|
142
|
+
|
|
143
|
+
- name: bool-method-prefix
|
|
144
|
+
type: function
|
|
145
|
+
filter: { return_type: bool }
|
|
146
|
+
naming: { prefix: [is_, has_, should_] }
|
|
147
|
+
|
|
148
|
+
- name: exception-naming
|
|
149
|
+
type: class
|
|
150
|
+
filter: { base_class: Exception }
|
|
151
|
+
naming: { regex: "^[A-Z][a-zA-Z]+(NotFound|Invalid|Denied|Conflict|Failed)Error$" }
|
|
152
|
+
|
|
153
|
+
- name: domain-module-naming
|
|
154
|
+
type: module
|
|
155
|
+
naming: { source: class_name, transform: snake_case }
|
|
156
|
+
|
|
157
|
+
- name: constant-upper-case
|
|
158
|
+
type: variable
|
|
159
|
+
filter: { target: constant }
|
|
160
|
+
naming: { case: UPPER_CASE }
|
|
161
|
+
|
|
162
|
+
apply:
|
|
163
|
+
- name: domain-layer
|
|
164
|
+
rules:
|
|
165
|
+
- attribute-matches-type
|
|
166
|
+
- bool-method-prefix
|
|
167
|
+
- domain-module-naming
|
|
168
|
+
- constant-upper-case
|
|
169
|
+
modules: contexts.*.domain
|
|
170
|
+
|
|
171
|
+
- name: global-exceptions
|
|
172
|
+
rules: [exception-naming]
|
|
173
|
+
modules: "**"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
### Rule Types
|
|
179
|
+
|
|
180
|
+
| Type | Target |
|
|
181
|
+
|------|--------|
|
|
182
|
+
| `variable` | Variable names (attribute, parameter, local_variable, constant) |
|
|
183
|
+
| `function` | Function/method names |
|
|
184
|
+
| `class` | Class names (including exceptions) |
|
|
185
|
+
| `module` | Module (file) names |
|
|
186
|
+
| `package` | Package (directory) names |
|
|
187
|
+
|
|
188
|
+
### Filter
|
|
189
|
+
|
|
190
|
+
Each rule can narrow its scope with type-specific filters:
|
|
191
|
+
|
|
192
|
+
| Type | Filter | Example Values |
|
|
193
|
+
|------|--------|----------------|
|
|
194
|
+
| `variable` | `target` | `attribute`, `parameter`, `local_variable`, `constant` |
|
|
195
|
+
| `function` | `target` | `method`, `function` |
|
|
196
|
+
| `function` | `return_type` | `bool` |
|
|
197
|
+
| `function` | `decorator` | `staticmethod` |
|
|
198
|
+
| `class` | `base_class` | `Exception` |
|
|
199
|
+
| `class` | `decorator` | `dataclass` |
|
|
200
|
+
|
|
201
|
+
### Naming Constraints
|
|
202
|
+
|
|
203
|
+
| Field | Description | Example |
|
|
204
|
+
|-------|-------------|---------|
|
|
205
|
+
| `prefix` | Name must start with one of the listed prefixes | `[is_, has_]` |
|
|
206
|
+
| `suffix` | Name must end with one of the listed suffixes | `[Repository, Service]` |
|
|
207
|
+
| `regex` | Name must match a regular expression | `"^[A-Z][a-zA-Z]+Error$"` |
|
|
208
|
+
| `source` + `transform` | Name must be derived from another element | `source: type_annotation`, `transform: snake_case` |
|
|
209
|
+
| `case` | Name must follow a casing convention | `snake_case`, `PascalCase`, `UPPER_CASE` |
|
|
210
|
+
|
|
211
|
+
### Include / Exclude
|
|
212
|
+
|
|
213
|
+
Control which files are scanned:
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
include:
|
|
217
|
+
- src
|
|
218
|
+
exclude:
|
|
219
|
+
- src/generated/**
|
|
220
|
+
|
|
221
|
+
rules:
|
|
222
|
+
- name: ...
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- **No `include` or `exclude`** — All `.py` files under the project root are scanned
|
|
226
|
+
- **`include` only** — Only files matching the given paths are scanned
|
|
227
|
+
- **`exclude` only** — All files except those matching the given paths are scanned
|
|
228
|
+
- **Both** — `include` is applied first, then `exclude` filters within that result
|
|
229
|
+
|
|
230
|
+
### Wildcard
|
|
231
|
+
|
|
232
|
+
`*` matches a single level in dotted module paths:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
modules: contexts.*.domain # matches contexts.boards.domain, contexts.auth.domain, ...
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`**` matches one or more levels:
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
modules: contexts.**.domain # matches contexts.boards.domain, contexts.boards.sub.domain, ...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Named Capture
|
|
245
|
+
|
|
246
|
+
`{name}` captures a single level (like `*`) and allows back-referencing:
|
|
247
|
+
|
|
248
|
+
```yaml
|
|
249
|
+
apply:
|
|
250
|
+
- name: domain-isolation
|
|
251
|
+
rules: [attribute-matches-type]
|
|
252
|
+
modules: contexts.{context}.domain
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### pyproject.toml
|
|
256
|
+
|
|
257
|
+
You can also configure in `pyproject.toml`:
|
|
258
|
+
|
|
259
|
+
```toml
|
|
260
|
+
[[tool.python-naming-linter.rules]]
|
|
261
|
+
name = "bool-method-prefix"
|
|
262
|
+
type = "function"
|
|
263
|
+
|
|
264
|
+
[tool.python-naming-linter.rules.filter]
|
|
265
|
+
return_type = "bool"
|
|
266
|
+
|
|
267
|
+
[tool.python-naming-linter.rules.naming]
|
|
268
|
+
prefix = ["is_", "has_", "should_"]
|
|
269
|
+
|
|
270
|
+
[[tool.python-naming-linter.apply]]
|
|
271
|
+
name = "all"
|
|
272
|
+
rules = ["bool-method-prefix"]
|
|
273
|
+
modules = "**"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## CLI
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Check with auto-discovered config (searches upward from cwd)
|
|
280
|
+
pnl check
|
|
281
|
+
|
|
282
|
+
# Specify config file (project root = config file's parent directory)
|
|
283
|
+
pnl check --config path/to/config.yaml
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Exit codes:
|
|
287
|
+
|
|
288
|
+
- `0` — No violations
|
|
289
|
+
- `1` — Violations found
|
|
290
|
+
- `2` — Config file not found
|
|
291
|
+
|
|
292
|
+
If no `--config` is given, the tool searches upward from the current directory for `.python-naming-linter.yaml` or `pyproject.toml` (with `[tool.python-naming-linter]`). The config file's parent directory is used as the project root.
|
|
293
|
+
|
|
294
|
+
## Pre-commit
|
|
295
|
+
|
|
296
|
+
Add to `.pre-commit-config.yaml`:
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
- repo: https://github.com/heumsi/python-naming-linter
|
|
300
|
+
rev: '' # Use the tag you want to point at (e.g., v0.1.0)
|
|
301
|
+
hooks:
|
|
302
|
+
- id: python-naming-linter
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
To pass custom options:
|
|
306
|
+
|
|
307
|
+
```yaml
|
|
308
|
+
- repo: https://github.com/heumsi/python-naming-linter
|
|
309
|
+
rev: ''
|
|
310
|
+
hooks:
|
|
311
|
+
- id: python-naming-linter
|
|
312
|
+
args: [--config, custom-config.yaml]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT
|