flake8-elegant-objects 1.0.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.
- flake8_elegant_objects-1.0.0/.github/workflows/lint.yml +33 -0
- flake8_elegant_objects-1.0.0/.github/workflows/publish.yml +54 -0
- flake8_elegant_objects-1.0.0/.github/workflows/test.yml +44 -0
- flake8_elegant_objects-1.0.0/.gitignore +161 -0
- flake8_elegant_objects-1.0.0/CLAUDE.md +59 -0
- flake8_elegant_objects-1.0.0/LICENSE +21 -0
- flake8_elegant_objects-1.0.0/PKG-INFO +216 -0
- flake8_elegant_objects-1.0.0/README.md +186 -0
- flake8_elegant_objects-1.0.0/docs/EO011.md +144 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/__init__.py +36 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/__main__.py +68 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/base.py +180 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_constructor_code.py +45 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_er_name.py +306 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_getters_setters.py +57 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_implementation_inheritance.py +75 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_impure_tests.py +127 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_mutable_objects.py +69 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_null.py +50 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_orm.py +97 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_public_methods_without_contracts.py +125 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_static.py +31 -0
- flake8_elegant_objects-1.0.0/flake8_elegant_objects/no_type_discrimination.py +34 -0
- flake8_elegant_objects-1.0.0/pyproject.toml +166 -0
- flake8_elegant_objects-1.0.0/tests/__init__.py +1 -0
- flake8_elegant_objects-1.0.0/tests/test_integration.py +248 -0
- flake8_elegant_objects-1.0.0/tests/test_no_constructor_code.py +133 -0
- flake8_elegant_objects-1.0.0/tests/test_no_er_name.py +157 -0
- flake8_elegant_objects-1.0.0/tests/test_no_getters_setters.py +186 -0
- flake8_elegant_objects-1.0.0/tests/test_no_implementation_inheritance.py +78 -0
- flake8_elegant_objects-1.0.0/tests/test_no_impure_tests.py +153 -0
- flake8_elegant_objects-1.0.0/tests/test_no_mutable_objects.py +167 -0
- flake8_elegant_objects-1.0.0/tests/test_no_null.py +126 -0
- flake8_elegant_objects-1.0.0/tests/test_no_orm.py +64 -0
- flake8_elegant_objects-1.0.0/tests/test_no_public_methods_without_contracts.py +277 -0
- flake8_elegant_objects-1.0.0/tests/test_no_static.py +53 -0
- flake8_elegant_objects-1.0.0/tests/test_no_type_discrimination.py +67 -0
- flake8_elegant_objects-1.0.0/uv.lock +316 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
run: uv python install 3.10
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync
|
|
25
|
+
|
|
26
|
+
- name: Run ruff check
|
|
27
|
+
run: uv run ruff check
|
|
28
|
+
|
|
29
|
+
- name: Run ruff format check
|
|
30
|
+
run: uv run ruff format --check
|
|
31
|
+
|
|
32
|
+
- name: Run type checking
|
|
33
|
+
run: uv run mypy .
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch: # Allow manual triggering
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: publish # Use environment for additional security
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write # For trusted publishing
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v3
|
|
22
|
+
with:
|
|
23
|
+
version: "latest"
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
run: uv python install 3.12
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: < /dev/null |
|
|
30
|
+
uv sync --dev
|
|
31
|
+
uv add --dev twine
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: |
|
|
35
|
+
uv run python -m pytest tests/ -v
|
|
36
|
+
uv run mypy flake8_elegant_objects/
|
|
37
|
+
uv run ruff check flake8_elegant_objects/
|
|
38
|
+
|
|
39
|
+
- name: Build package
|
|
40
|
+
run: uv build
|
|
41
|
+
|
|
42
|
+
- name: Publish to TestPyPI
|
|
43
|
+
env:
|
|
44
|
+
TWINE_USERNAME: __token__
|
|
45
|
+
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
46
|
+
run: |
|
|
47
|
+
uv run twine upload --repository testpypi dist/* --skip-existing
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
env:
|
|
51
|
+
TWINE_USERNAME: __token__
|
|
52
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
53
|
+
run: |
|
|
54
|
+
uv run twine upload dist/*
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Build and Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v4
|
|
22
|
+
with:
|
|
23
|
+
version: 'latest'
|
|
24
|
+
|
|
25
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
26
|
+
run: uv python install ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: uv sync
|
|
30
|
+
|
|
31
|
+
- name: Run linting
|
|
32
|
+
run: uv run ruff check .
|
|
33
|
+
|
|
34
|
+
- name: Run formatting check
|
|
35
|
+
run: uv run ruff format --check .
|
|
36
|
+
|
|
37
|
+
- name: Run type checking
|
|
38
|
+
run: uv run mypy .
|
|
39
|
+
|
|
40
|
+
- name: Run unit tests
|
|
41
|
+
run: uv run pytest tests/ -v
|
|
42
|
+
|
|
43
|
+
- name: Build package
|
|
44
|
+
run: uv build
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
db.sqlite3
|
|
60
|
+
db.sqlite3-journal
|
|
61
|
+
|
|
62
|
+
# Flask stuff:
|
|
63
|
+
instance/
|
|
64
|
+
.webassets-cache
|
|
65
|
+
|
|
66
|
+
# Scrapy stuff:
|
|
67
|
+
.scrapy
|
|
68
|
+
|
|
69
|
+
# Sphinx documentation
|
|
70
|
+
docs/_build/
|
|
71
|
+
|
|
72
|
+
# PyBuilder
|
|
73
|
+
.pybuilder/
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# IPython
|
|
80
|
+
profile_default/
|
|
81
|
+
ipython_config.py
|
|
82
|
+
|
|
83
|
+
# pyenv
|
|
84
|
+
.python-version
|
|
85
|
+
|
|
86
|
+
# pipenv
|
|
87
|
+
Pipfile.lock
|
|
88
|
+
|
|
89
|
+
# poetry
|
|
90
|
+
poetry.lock
|
|
91
|
+
|
|
92
|
+
# pdm
|
|
93
|
+
.pdm.toml
|
|
94
|
+
|
|
95
|
+
# PEP 582
|
|
96
|
+
__pypackages__/
|
|
97
|
+
|
|
98
|
+
# Celery stuff
|
|
99
|
+
celerybeat-schedule
|
|
100
|
+
celerybeat.pid
|
|
101
|
+
|
|
102
|
+
# SageMath parsed files
|
|
103
|
+
*.sage.py
|
|
104
|
+
|
|
105
|
+
# Environments
|
|
106
|
+
.env
|
|
107
|
+
.venv
|
|
108
|
+
env/
|
|
109
|
+
venv/
|
|
110
|
+
ENV/
|
|
111
|
+
env.bak/
|
|
112
|
+
venv.bak/
|
|
113
|
+
|
|
114
|
+
# Spyder project settings
|
|
115
|
+
.spyderproject
|
|
116
|
+
.spyproject
|
|
117
|
+
|
|
118
|
+
# Rope project settings
|
|
119
|
+
.ropeproject
|
|
120
|
+
|
|
121
|
+
# mkdocs documentation
|
|
122
|
+
/site
|
|
123
|
+
|
|
124
|
+
# mypy
|
|
125
|
+
.mypy_cache/
|
|
126
|
+
.dmypy.json
|
|
127
|
+
dmypy.json
|
|
128
|
+
|
|
129
|
+
# Pyre type checker
|
|
130
|
+
.pyre/
|
|
131
|
+
|
|
132
|
+
# pytype static type analyzer
|
|
133
|
+
.pytype/
|
|
134
|
+
|
|
135
|
+
# Cython debug symbols
|
|
136
|
+
cython_debug/
|
|
137
|
+
|
|
138
|
+
# Ruff
|
|
139
|
+
.ruff_cache/
|
|
140
|
+
|
|
141
|
+
# IDE / Editor files
|
|
142
|
+
.vscode/
|
|
143
|
+
.idea/
|
|
144
|
+
*.swp
|
|
145
|
+
*.swo
|
|
146
|
+
*~
|
|
147
|
+
|
|
148
|
+
# OS generated files
|
|
149
|
+
.DS_Store
|
|
150
|
+
.DS_Store?
|
|
151
|
+
._*
|
|
152
|
+
.Spotlight-V100
|
|
153
|
+
.Trashes
|
|
154
|
+
ehthumbs.db
|
|
155
|
+
Thumbs.db
|
|
156
|
+
|
|
157
|
+
# Temporary files
|
|
158
|
+
*.tmp
|
|
159
|
+
*.temp
|
|
160
|
+
*.bak
|
|
161
|
+
*.orig
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Development Commands
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Testing
|
|
7
|
+
python -m pytest tests/ -v
|
|
8
|
+
|
|
9
|
+
# Code Quality
|
|
10
|
+
mypy flake8_elegant_objects/
|
|
11
|
+
ruff check flake8_elegant_objects/
|
|
12
|
+
ruff format flake8_elegant_objects/
|
|
13
|
+
|
|
14
|
+
# Plugin Usage
|
|
15
|
+
python -m flake8_elegant_objects --show-source path/to/files/*.py
|
|
16
|
+
flake8 --select=EO path/to/files/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
Flake8 plugin enforcing Elegant Objects principles (14 error codes EO001-EO014).
|
|
22
|
+
|
|
23
|
+
**Core Components:**
|
|
24
|
+
- `__init__.py`: ElegantObjectsPlugin orchestrating analysis
|
|
25
|
+
- `base.py`: Principles base class, Violations type, ErrorCodes
|
|
26
|
+
- `naming.py`: NoErNamePrinciple (EO001-EO004)
|
|
27
|
+
- `core.py`: CorePrinciples (EO005-EO008)
|
|
28
|
+
- `advanced.py`: AdvancedPrinciples (EO009-EO014)
|
|
29
|
+
|
|
30
|
+
**Pattern:** Plugin uses Principles which provides Violations (no None)
|
|
31
|
+
|
|
32
|
+
## Elegant Objects Principles
|
|
33
|
+
|
|
34
|
+
**MUST follow:**
|
|
35
|
+
- No null (None) - use empty lists instead
|
|
36
|
+
- No code in constructors
|
|
37
|
+
- No getters/setters
|
|
38
|
+
- No mutable objects
|
|
39
|
+
- NO "-ER" NAMES: Manager, Controller, Helper, Handler, Parser, etc
|
|
40
|
+
- No static methods
|
|
41
|
+
- No instanceof/type casting
|
|
42
|
+
- No public methods without contracts
|
|
43
|
+
- No statements in test methods except assertThat
|
|
44
|
+
- No ORM/ActiveRecord
|
|
45
|
+
- No implementation inheritance
|
|
46
|
+
|
|
47
|
+
**Philosophy:**
|
|
48
|
+
- Objects are living partners, not data containers
|
|
49
|
+
- Declarative over imperative (`new Sorted(apples)` not `new Sorter().sort(apples)`)
|
|
50
|
+
- Fail fast with exceptions, not null checks
|
|
51
|
+
- Composition over inheritance
|
|
52
|
+
- Immutability implied by design, not keywords
|
|
53
|
+
|
|
54
|
+
## Code Style
|
|
55
|
+
|
|
56
|
+
- No useless comments
|
|
57
|
+
- Types: `Violations = list[Violation]`
|
|
58
|
+
- Return empty lists `[]` instead of `None`
|
|
59
|
+
- Extend lists instead of checking for None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Anton Prokopyev
|
|
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,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flake8-elegant-objects
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Flake8 plugin enforcing Elegant Objects principles: no -er naming, no null, no getters/setters, immutable objects
|
|
5
|
+
Project-URL: Homepage, https://github.com/AntonProkopyev/flake8-elegant-objects
|
|
6
|
+
Project-URL: Repository, https://github.com/AntonProkopyev/flake8-elegant-objects.git
|
|
7
|
+
Project-URL: Issues, https://github.com/AntonProkopyev/flake8-elegant-objects/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/AntonProkopyev/flake8-elegant-objects#readme
|
|
9
|
+
Author: Flake8 Elegant Objects Contributors
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: code-quality,elegant-objects,flake8,linting,object-oriented,python,static-analysis
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Framework :: Flake8
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Flake8 ElegantObjects Plugin
|
|
32
|
+
|
|
33
|
+
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
34
|
+
[](https://github.com/AntonProkopyev/flake8-elegant-objects)
|
|
35
|
+
[](https://python.org)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://github.com/astral-sh/ruff)
|
|
38
|
+
[](https://mypy.readthedocs.io/)
|
|
39
|
+
[](https://flake8.pycqa.org/)
|
|
40
|
+
|
|
41
|
+
Detects violations of core Elegant Objects principles including the "-er" naming principle, null usage, mutable objects, code in constructors, and getter/setter patterns.
|
|
42
|
+
|
|
43
|
+
## Error Codes
|
|
44
|
+
|
|
45
|
+
- `EO001`: Class name violates -er principle
|
|
46
|
+
- `EO002`: Method name violates -er principle
|
|
47
|
+
- `EO003`: Variable name violates -er principle
|
|
48
|
+
- `EO004`: Function name violates -er principle
|
|
49
|
+
- `EO005`: Null (None) usage violates EO principle
|
|
50
|
+
- `EO006`: Code in constructor violates EO principle
|
|
51
|
+
- `EO007`: Getter/setter method violates EO principle
|
|
52
|
+
- `EO008`: Mutable object violation
|
|
53
|
+
- `EO009`: Static method violates EO principle (no static methods allowed)
|
|
54
|
+
- `EO010`: isinstance/type casting violates EO principle (avoid type discrimination)
|
|
55
|
+
- `EO011`: Public method without contract (Protocol/ABC) violates EO principle
|
|
56
|
+
- `EO012`: Test method contains non-assertThat statements (only assertThat allowed)
|
|
57
|
+
- `EO013`: ORM/ActiveRecord pattern violates EO principle
|
|
58
|
+
- `EO014`: Implementation inheritance violates EO principle
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install flake8-elegant-objects
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
**Standalone:**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python -m flake8_elegant_objects path/to/files/*.py
|
|
72
|
+
python -m flake8_elegant_objects --show-source path/to/files/*.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**As flake8 plugin:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
flake8 --select=EO path/to/files/
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The plugin is automatically registered when the package is installed.
|
|
82
|
+
|
|
83
|
+
## Philosophy
|
|
84
|
+
|
|
85
|
+
Based on Yegor Bugayenko's Elegant Objects principles, this plugin enforces object-oriented design that treats objects as living, thinking entities rather than data containers or procedure executors.
|
|
86
|
+
|
|
87
|
+
### 1. No "-er" Entities (EO001-EO004)
|
|
88
|
+
|
|
89
|
+
**Why?** Names ending in "-er" describe what objects _do_ rather than what they _are_, reducing them to mechanical task performers instead of equal partners in your design.
|
|
90
|
+
|
|
91
|
+
- ❌ `class DataProcessor` → ✅ `class ProcessedData`
|
|
92
|
+
- ❌ `def analyze()` → ✅ `def analysis()`
|
|
93
|
+
- ❌ `parser = ArgumentParser()` → ✅ `arguments = ArgumentParser()`
|
|
94
|
+
|
|
95
|
+
### 2. No Null/None (EO005)
|
|
96
|
+
|
|
97
|
+
**Why?** Null references break object-oriented thinking by representing "absence of object" - but absence cannot participate in object interactions. They lead to defensive programming and unclear contracts.
|
|
98
|
+
|
|
99
|
+
- ❌ `return None` → ✅ Return null objects with safe default behavior
|
|
100
|
+
- ❌ `if user is None:` → ✅ Use null object pattern or throw exceptions
|
|
101
|
+
|
|
102
|
+
### 3. No Code in Constructors (EO006)
|
|
103
|
+
|
|
104
|
+
**Why?** Constructors should be about object assembly, not computation. Complex logic in constructors violates the principle that objects should be lazy and do work only when asked.
|
|
105
|
+
|
|
106
|
+
- ❌ `self.name = name.upper()` → ✅ `self.name = name` (transform on access)
|
|
107
|
+
- ❌ `self.items = [process(x) for x in data]` → ✅ `self.data = data` (process lazily)
|
|
108
|
+
|
|
109
|
+
### 4. No Getters/Setters (EO007)
|
|
110
|
+
|
|
111
|
+
**Why?** Getters and setters expose internal state, breaking encapsulation. They encourage "tell, don't ask" violations and treat objects as data containers rather than behavioral entities.
|
|
112
|
+
|
|
113
|
+
- ❌ `def get_value()` / `def set_value()` → ✅ Objects should expose behavior, not data
|
|
114
|
+
- ❌ `user.getName()` → ✅ `user.introduce_yourself()` or `user.greet(visitor)`
|
|
115
|
+
|
|
116
|
+
### 5. No Mutable Objects (EO008)
|
|
117
|
+
|
|
118
|
+
**Why?** Mutable objects introduce temporal coupling and make reasoning about code difficult. Immutable objects are thread-safe, predictable, and easier to test.
|
|
119
|
+
|
|
120
|
+
- ❌ `@dataclass class Data` → ✅ `@dataclass(frozen=True) class Data`
|
|
121
|
+
- ❌ `items = []` → ✅ `items: tuple = ()`
|
|
122
|
+
|
|
123
|
+
### 6. No Static Methods (EO009)
|
|
124
|
+
|
|
125
|
+
**Why?** Static methods belong to classes, not objects, breaking object-oriented design. They can't be overridden, can't be mocked easily, and promote procedural thinking. Every static method is a candidate for a new class.
|
|
126
|
+
|
|
127
|
+
- ❌ `@staticmethod def process()` → ✅ Create dedicated objects for behavior
|
|
128
|
+
- ❌ `Math.sqrt(x)` → ✅ `SquareRoot(x).value()`
|
|
129
|
+
|
|
130
|
+
### 7. No Type Discrimination (EO010)
|
|
131
|
+
|
|
132
|
+
**Why?** Using `isinstance`, type casting, or reflection is a form of object discrimination. It violates polymorphism by treating objects unequally based on their type rather than their behavior contracts.
|
|
133
|
+
|
|
134
|
+
- ❌ `isinstance(obj, str)` → ✅ Design common interfaces and use polymorphism
|
|
135
|
+
- ❌ `if type(x) == int:` → ✅ Let objects decide how to behave
|
|
136
|
+
|
|
137
|
+
### 8. No Public Methods Without Contracts (EO011)
|
|
138
|
+
|
|
139
|
+
**Why?** Public methods without explicit contracts (Protocol/ABC) create implicit dependencies and unclear expectations. Contracts make object collaboration explicit and testable.
|
|
140
|
+
|
|
141
|
+
- ❌ `class Service:` with ad-hoc public methods → ✅ `class Service(Protocol):` with defined contracts
|
|
142
|
+
- ❌ Implicit interfaces → ✅ Explicit protocols that can be tested and verified
|
|
143
|
+
|
|
144
|
+
### 9. Test Methods: Only assertThat (EO012)
|
|
145
|
+
|
|
146
|
+
**Why?** Test methods should contain only one assertion statement (preferably `assertThat`). Multiple statements create complex tests that are hard to understand and maintain. Each test should verify one specific behavior.
|
|
147
|
+
|
|
148
|
+
- ❌ `x = 5; y = calculate(x); assert y > 0` → ✅ `assertThat(calculate(5), is_(greater_than(0)))`
|
|
149
|
+
- ❌ Multiple assertions per test → ✅ One focused assertion per test
|
|
150
|
+
|
|
151
|
+
### 10. No ORM/ActiveRecord (EO013)
|
|
152
|
+
|
|
153
|
+
**Why?** ORM and ActiveRecord patterns mix data persistence concerns with business logic, violating single responsibility. They create anemic domain models and tight coupling to databases.
|
|
154
|
+
|
|
155
|
+
- ❌ `user.save()`, `Model.find()` → ✅ Separate repository objects
|
|
156
|
+
- ❌ Mixing persistence with business logic → ✅ Clean separation of concerns
|
|
157
|
+
|
|
158
|
+
### 11. No Implementation Inheritance (EO014)
|
|
159
|
+
|
|
160
|
+
**Why?** Implementation inheritance creates tight coupling between parent and child classes, making code fragile and hard to test. It violates composition over inheritance and creates deep hierarchies that are difficult to understand.
|
|
161
|
+
|
|
162
|
+
- ❌ `class UserList(list):` → ✅ `class UserList:` with composition
|
|
163
|
+
- ❌ Inheriting concrete implementations → ✅ Inherit only from abstractions (ABC/Protocol)
|
|
164
|
+
|
|
165
|
+
The plugin detects the "hall of shame" naming patterns: Manager, Controller, Helper, Handler, Writer, Reader, Converter, Validator, Router, Dispatcher, Observer, Listener, Sorter, Encoder, Decoder, Analyzer, etc.
|
|
166
|
+
|
|
167
|
+
## Configuration
|
|
168
|
+
|
|
169
|
+
The plugin is integrated with flake8. Add to your `.flake8` config:
|
|
170
|
+
|
|
171
|
+
```ini
|
|
172
|
+
[flake8]
|
|
173
|
+
select = E,W,F,EO
|
|
174
|
+
per-file-ignores =
|
|
175
|
+
tests/*:EO012 # Allow non-assertThat in tests if needed
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
### Testing
|
|
181
|
+
|
|
182
|
+
Run all tests:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python -m pytest tests/ -v
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Code Quality
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Type checking
|
|
192
|
+
mypy flake8_elegant_objects/
|
|
193
|
+
|
|
194
|
+
# Linting and formatting
|
|
195
|
+
ruff check flake8_elegant_objects/
|
|
196
|
+
ruff format flake8_elegant_objects/
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Project Structure
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
flake8_elegant_objects/
|
|
203
|
+
├── __init__.py # Main plugin entry point
|
|
204
|
+
├── base.py # Base classes and utilities
|
|
205
|
+
├── no_er_name.py # EO001-EO004: No "-er" names
|
|
206
|
+
├── no_null.py # EO005: No None usage
|
|
207
|
+
├── no_constructor_code.py # EO006: No code in constructors
|
|
208
|
+
├── no_getters_setters.py # EO007: No getters/setters
|
|
209
|
+
├── no_mutable_objects.py # EO008: No mutable objects
|
|
210
|
+
├── no_static.py # EO009: No static methods
|
|
211
|
+
├── no_type_discrimination.py # EO010: No isinstance/type casting
|
|
212
|
+
├── no_public_methods_without_contracts.py # EO011: Contracts required
|
|
213
|
+
├── no_impure_tests.py # EO012: Only assertThat in tests
|
|
214
|
+
├── no_orm.py # EO013: No ORM/ActiveRecord
|
|
215
|
+
└── no_implementation_inheritance.py # EO014: No implementation inheritance
|
|
216
|
+
```
|