pytest-eo 0.5.1__py3-none-any.whl

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.
Files changed (67) hide show
  1. pytest_eo/__init__.py +1 -0
  2. pytest_eo/angry_tests.py +44 -0
  3. pytest_eo/assertion.py +30 -0
  4. pytest_eo/assigned.py +32 -0
  5. pytest_eo/blank_lines.py +42 -0
  6. pytest_eo/bodies.py +36 -0
  7. pytest_eo/bundled.py +20 -0
  8. pytest_eo/callee.py +23 -0
  9. pytest_eo/capacity.py +50 -0
  10. pytest_eo/check.py +17 -0
  11. pytest_eo/command.py +13 -0
  12. pytest_eo/comment.py +42 -0
  13. pytest_eo/comments.py +42 -0
  14. pytest_eo/config/flake8.ini +23 -0
  15. pytest_eo/config/mypy.ini +5 -0
  16. pytest_eo/config/ruff.toml +43 -0
  17. pytest_eo/edges.py +28 -0
  18. pytest_eo/encapsulation.py +30 -0
  19. pytest_eo/ending.py +30 -0
  20. pytest_eo/eo_item.py +41 -0
  21. pytest_eo/eo_table.py +29 -0
  22. pytest_eo/excludes.py +25 -0
  23. pytest_eo/extension.py +12 -0
  24. pytest_eo/fields.py +40 -0
  25. pytest_eo/files.py +12 -0
  26. pytest_eo/final_assertion.py +46 -0
  27. pytest_eo/flake_check.py +49 -0
  28. pytest_eo/heading.py +35 -0
  29. pytest_eo/inline_comments.py +44 -0
  30. pytest_eo/layout.py +15 -0
  31. pytest_eo/margin.py +42 -0
  32. pytest_eo/message.py +60 -0
  33. pytest_eo/module_constants.py +32 -0
  34. pytest_eo/mypy_check.py +43 -0
  35. pytest_eo/narrow_suppressions.py +72 -0
  36. pytest_eo/negative_messages.py +40 -0
  37. pytest_eo/output.py +37 -0
  38. pytest_eo/owned.py +28 -0
  39. pytest_eo/place.py +21 -0
  40. pytest_eo/plugin.py +45 -0
  41. pytest_eo/printout.py +18 -0
  42. pytest_eo/profile.py +13 -0
  43. pytest_eo/project.py +37 -0
  44. pytest_eo/project_files.py +39 -0
  45. pytest_eo/python_module.py +36 -0
  46. pytest_eo/raising.py +27 -0
  47. pytest_eo/remark.py +25 -0
  48. pytest_eo/ruff_check.py +50 -0
  49. pytest_eo/ruff_format_check.py +46 -0
  50. pytest_eo/rule.py +12 -0
  51. pytest_eo/shared.py +35 -0
  52. pytest_eo/span.py +28 -0
  53. pytest_eo/strict.py +112 -0
  54. pytest_eo/style_violations_error.py +15 -0
  55. pytest_eo/suppression_check.py +44 -0
  56. pytest_eo/table.py +11 -0
  57. pytest_eo/text.py +28 -0
  58. pytest_eo/tool_failed_error.py +15 -0
  59. pytest_eo/trial.py +37 -0
  60. pytest_eo/verified.py +28 -0
  61. pytest_eo/violation.py +24 -0
  62. pytest_eo/wide_suppressions.py +45 -0
  63. pytest_eo-0.5.1.dist-info/METADATA +177 -0
  64. pytest_eo-0.5.1.dist-info/RECORD +67 -0
  65. pytest_eo-0.5.1.dist-info/WHEEL +4 -0
  66. pytest_eo-0.5.1.dist-info/entry_points.txt +7 -0
  67. pytest_eo-0.5.1.dist-info/licenses/LICENSE.txt +21 -0
pytest_eo/trial.py ADDED
@@ -0,0 +1,37 @@
1
+ """The test functions pytest would run."""
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import final
6
+
7
+ from typing_extensions import override
8
+
9
+
10
+ @final
11
+ @dataclass(frozen=True, slots=True)
12
+ class Trial(ast.NodeVisitor): # noqa: ENC100 - a visitor holds no state, ast dispatches on the node
13
+ """A function whose name makes pytest run it; nothing for other nodes."""
14
+
15
+ @override
16
+ def generic_visit(
17
+ self,
18
+ node: ast.AST,
19
+ ) -> tuple[ast.FunctionDef | ast.AsyncFunctionDef, ...]:
20
+ """Find no test in a node that isnt a function."""
21
+ return ()
22
+
23
+ @override
24
+ def visit_FunctionDef(
25
+ self,
26
+ node: ast.FunctionDef,
27
+ ) -> tuple[ast.FunctionDef | ast.AsyncFunctionDef, ...]:
28
+ """Find the function when its name starts with ``test``."""
29
+ return (node,) if node.name.startswith("test") else ()
30
+
31
+ @override
32
+ def visit_AsyncFunctionDef(
33
+ self,
34
+ node: ast.AsyncFunctionDef,
35
+ ) -> tuple[ast.FunctionDef | ast.AsyncFunctionDef, ...]:
36
+ """Find the coroutine when its name starts with ``test``."""
37
+ return (node,) if node.name.startswith("test") else ()
pytest_eo/verified.py ADDED
@@ -0,0 +1,28 @@
1
+ """A command that fails loudly unless it exits as expected."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import final
5
+
6
+ from typing_extensions import override
7
+
8
+ from pytest_eo.command import Command
9
+ from pytest_eo.printout import Printout
10
+
11
+
12
+ @final
13
+ @dataclass(frozen=True, slots=True)
14
+ class Verified(Command):
15
+ """A tool run that refuses any exit status its reader does not expect."""
16
+
17
+ _origin: Command
18
+ _tool: str
19
+ _allowed: tuple[int, ...]
20
+
21
+ @override
22
+ def run(self) -> Printout:
23
+ """Run the command and fail on a status outside of the allowed ones."""
24
+ output = self._origin.run()
25
+ if not output.exited(self._allowed):
26
+ reason = f"{self._tool} exited unexpectedly"
27
+ raise output.failure(reason)
28
+ return output
pytest_eo/violation.py ADDED
@@ -0,0 +1,24 @@
1
+ """A single style violation reported by one of the aggregated tools."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import final
5
+
6
+ from typing_extensions import override
7
+
8
+ from pytest_eo.place import Place
9
+
10
+
11
+ @final
12
+ @dataclass(frozen=True, slots=True)
13
+ class Violation:
14
+ """What is wrong, where, and which tool says so."""
15
+
16
+ _tool: str
17
+ _code: str
18
+ _place: Place
19
+ _message: str
20
+
21
+ @override
22
+ def __str__(self) -> str:
23
+ """Render as ``path:line:col: [tool:code] message``."""
24
+ return f"{self._place}: [{self._tool}:{self._code}] {self._message}"
@@ -0,0 +1,45 @@
1
+ """The suppressions that reach beyond their own line."""
2
+
3
+ import re
4
+ from collections.abc import Iterable, Iterator
5
+ from dataclasses import dataclass
6
+ from typing import final
7
+
8
+ from typing_extensions import override
9
+
10
+ from pytest_eo.remark import Remark
11
+ from pytest_eo.violation import Violation
12
+
13
+ _SPREAD = r"ruff:\s*(?:noqa|file-ignore|disable)\b|flake8:\s*noqa\b|mypy:"
14
+ _WIDE = re.compile(rf"#\s*(?P<kind>{_SPREAD})")
15
+ _BARE = re.compile(r"#\s*(?P<kind>type:\s*ignore\b(?!\[))")
16
+
17
+
18
+ @final
19
+ @dataclass(frozen=True, slots=True)
20
+ class WideSuppressions(Iterable[Violation]):
21
+ """File-wide and range forms of ruff, flake8 and mypy in one comment.
22
+
23
+ Above the first code a bare ``# type: ignore`` silences the whole file,
24
+ so there it counts as one of them.
25
+ """
26
+
27
+ _tool: str
28
+ _comment: Remark
29
+
30
+ @override
31
+ def __iter__(self) -> Iterator[Violation]:
32
+ """Report every form of the comment that suppresses beyond its line."""
33
+ return (
34
+ Violation(
35
+ self._tool,
36
+ "SUP003",
37
+ place,
38
+ f"`# {match['kind']}` suppresses beyond its own line: "
39
+ "suppress on that one line instead, with a reason",
40
+ )
41
+ for place, match in (
42
+ *self._comment.matches(_WIDE),
43
+ *self._comment.heading(_BARE),
44
+ )
45
+ )
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.5
2
+ Name: pytest-eo
3
+ Version: 0.5.1
4
+ Summary: pytest --eo: strict style gate over ruff, mypy --strict and pyeo
5
+ Author: RaulIQ
6
+ License-Expression: MIT
7
+ License-File: LICENSE.txt
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: eo-styleguide==0.2.0
10
+ Requires-Dist: flake8-override==0.1.1
11
+ Requires-Dist: flake8==7.4.1
12
+ Requires-Dist: mypy==2.4.0
13
+ Requires-Dist: pyhamcrest>=2.1
14
+ Requires-Dist: pytest>=8
15
+ Requires-Dist: ruff==0.17.1
16
+ Requires-Dist: typing-extensions>=4.4
17
+ Provides-Extra: dev
18
+ Requires-Dist: coverage>=7.10; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # pytest-eo
22
+
23
+ [![codecov](https://codecov.io/gh/RaulIQ/pytest-eo/graph/badge.svg)](https://codecov.io/gh/RaulIQ/pytest-eo)
24
+
25
+ `pytest --eo` is a strict quality gate in the spirit of
26
+ [Qulice](https://github.com/yegor256/qulice), built from ready-made tools.
27
+ The plugin runs the tools, reads their reports, and shows each tool as a
28
+ separate test; its own rules cover only tests and suppressions.
29
+
30
+ | Test | Tool |
31
+ |-------------------|------------------------------------------------|
32
+ | `eo::ruff` | `ruff check`, all rules (`select = ["ALL"]`) |
33
+ | `eo::ruff-format` | `ruff format --check` |
34
+ | `eo::mypy` | `mypy --strict` |
35
+ | `eo::flake8` | `flake8` with [pyeo](https://pypi.org/project/eo-styleguide/) (Elegant Objects rules) |
36
+ | `eo::suppressions`| `# noqa` and `# ruff: ignore` comments |
37
+
38
+ The Elegant Objects rules enabled so far, most of them from
39
+ [pyeo](https://github.com/blablatdinov/pyeo) and the rest from its sibling
40
+ packages:
41
+
42
+ | Code | Rule | Package |
43
+ |----------|----------------------------------------------|---------|
44
+ | `FIN100` | every class is `@final` | pyeo |
45
+ | `PEO101` | `__init__` contains only assignments | pyeo |
46
+ | `PEO102` | `@classmethod` contains only a `cls()` call | pyeo |
47
+ | `PEO200` | every class is frozen (immutable) | pyeo |
48
+ | `PEO300` | no class names ending in "-er" | pyeo |
49
+ | `PEO301` | no public attributes | pyeo |
50
+ | `PEO400` | no static methods | pyeo |
51
+ | `PEO500` | no `@property`, a getter in disguise | pyeo |
52
+ | `PEO601` | no methods starting with `get` | pyeo |
53
+ | `PEO602` | no getter methods | pyeo |
54
+ | `PEO700` | no introspection, casting, or reflection | pyeo |
55
+ | `NPM100` | no private functions or methods | [flake8-no-private-methods](https://github.com/blablatdinov/flake8-no-private-methods) |
56
+ | `OVR100` | every public method is an `@override` of an interface method | [flake8-override](https://github.com/blablatdinov/flake8-override) |
57
+ | `FOC100` | one public class per module | [flake8-one-class](https://github.com/blablatdinov/flake8-one-class) |
58
+
59
+ mypy runs with `explicit-override` and `warn_unreachable` on top of
60
+ `--strict`: an `@override` must override a real method, every overriding
61
+ method must say so, and code that can never run is an error.
62
+ Python 3.11 has no `typing.override`; import it from `typing_extensions`.
63
+
64
+ The package also brings a flake8 plugin of its own, with rules in the spirit
65
+ of "Angry Tests" for the files pytest collects (`test_*.py`, `*_test.py`):
66
+
67
+ | Code | Rule |
68
+ |----------|----------------------------------------------------------|
69
+ | `TST100` | a test ends with its only assertion, or with `pytest.raises` |
70
+ | `TST200` | a test module keeps no constants, except `pytest_plugins` |
71
+ | `TST300` | an assertion carries a negative message, such as `"x doesnt y"` |
72
+
73
+ More rules of its own cover every file, tests or not:
74
+
75
+ | Code | Rule |
76
+ |----------|----------------------------------------------------------|
77
+ | `ENC100` | a class encapsulates one to four attributes; a `Protocol` is exempt |
78
+ | `BOD100` | a function body holds no blank lines, except around a nested `def` or `class` |
79
+ | `BOD200` | a function body holds no comments, except suppressions |
80
+
81
+ As in Qulice, which rejects plain JUnit assertions, a test asserts through a
82
+ Hamcrest matcher, never through a plain `assert`; ruff's `S101` stays on in
83
+ tests too. PyHamcrest comes with the package:
84
+
85
+ ```python
86
+ from hamcrest import assert_that, equal_to
87
+
88
+
89
+ def test_checks_the_whole_directory_by_default(tmp_path: Path) -> None:
90
+ assert_that(
91
+ Project(EoTable(tmp_path / "pyproject.toml"), tmp_path).paths(),
92
+ equal_to((".",)),
93
+ "Project doesnt check the whole directory by default",
94
+ )
95
+ ```
96
+
97
+ A rule may be suppressed on one line only, with a reason:
98
+ `# noqa: PEO200 - pytest.Item is mutable by design`, or
99
+ `# ruff: ignore[unused-import] - re-exported for plugins`.
100
+ `eo::suppressions` enforces it:
101
+
102
+ | Code | Rule |
103
+ |----------|----------------------------------------------------------|
104
+ | `SUP001` | a suppression gives a reason after ` - ` |
105
+ | `SUP002` | a suppression never names `TID251` (`banned-api`), the ban on mocks |
106
+ | `SUP003` | nothing suppresses past one line: no `# ruff: noqa`, `file-ignore` or `disable`, no `# flake8: noqa`, no `# mypy:` comment, no `# type: ignore` above the code |
107
+
108
+ ruff already rejects a bare `# noqa` (`PGH004`), a bare `# type: ignore`
109
+ (`PGH003`) and an unused `# noqa` for its own codes (`RUF100`).
110
+
111
+ The configuration is bundled with the package (`src/pytest_eo/config/`)
112
+ and cannot be overridden by a project. A project may only say what to
113
+ check and what to skip:
114
+
115
+ ```toml
116
+ [tool.eo]
117
+ paths = ["src", "tests"] # default: src and tests, if they exist
118
+ exclude = ["generated/"]
119
+ ```
120
+
121
+ ## Usage
122
+
123
+ ```sh
124
+ pip install 'pytest-eo==0.5.1'
125
+ pytest --eo
126
+ ```
127
+
128
+ Without `--eo` the plugin does nothing. Pin an exact version: every version
129
+ of the rules is a version of the package, so pip installs new rules only
130
+ when the version in the requirement changes.
131
+
132
+ ## Claude Code hooks
133
+
134
+ This repository runs the gate on every Claude Code session that works in it:
135
+ after Claude edits a `.py`, `.toml`, `.ini` or `.cfg` file, and again before
136
+ it finishes. When the gate is red, the findings go back to Claude and it has
137
+ to fix them. If the gate is still red after one retry, Claude may stop, and
138
+ you see a warning instead.
139
+
140
+ To get the same in another project:
141
+
142
+ 1. Install pytest-eo into the project's `.venv`.
143
+ 2. Copy `.claude/hooks/eo-gate.sh` into the same place in that project and
144
+ make it executable.
145
+ 3. Copy the `hooks` block of `.claude/settings.json` into that project's
146
+ `.claude/settings.json`, merging it with what is already there.
147
+ 4. Commit both files, so everyone who opens the project with Claude Code gets
148
+ the gate, and run `/hooks` once in an open session to load them.
149
+
150
+ ## Development
151
+
152
+ ```sh
153
+ python3 -m venv .venv
154
+ .venv/bin/pip install -e '.[dev]'
155
+ make check
156
+ ```
157
+
158
+ `make check` runs the tests and `pytest --eo` on the plugin itself; Rultor
159
+ merges on it. `make coverage` measures lines and branches, counting the
160
+ pytest subprocesses of the end-to-end tests too, and writes `coverage.xml`,
161
+ which GitHub Actions sends to Codecov on every push and pull request.
162
+
163
+ `pyproject.toml` on `main` always says `version = "0.0.0"`; a real version
164
+ exists only in a tag. To release, comment in any issue, with the version in
165
+ backticks, since Rultor reads nothing else:
166
+
167
+ ```text
168
+ @rultor release, tag is `0.3.0`
169
+ ```
170
+
171
+ Rultor writes the version into `pyproject.toml`, runs
172
+ `make check`, commits the result on a side branch and pushes only the tag.
173
+ GitHub Actions builds that tag and uploads it to PyPI through Trusted
174
+ Publishing, so no token is stored anywhere.
175
+ Every change to the rules or the bundled configuration ships in a release:
176
+ raise the minor number for a new or stricter rule and the patch number for a
177
+ fix.
@@ -0,0 +1,67 @@
1
+ pytest_eo/__init__.py,sha256=3rmPJGpub_1Gi1GNn-2SRWudJqrWUik011WgAs6sNd4,73
2
+ pytest_eo/angry_tests.py,sha256=v13cAE_Wux3w4-iKiD4c4-kWypgsWO9dyeo2-QYMRjE,1544
3
+ pytest_eo/assertion.py,sha256=Iot15iPLIYa9HXB03EKFomjmcZ4CxgjzVTALz7sc-5M,908
4
+ pytest_eo/assigned.py,sha256=lO7ZaLEQ29GXXtkSSwnNOjPQHsH881vJRUXc6fZwcHk,1070
5
+ pytest_eo/blank_lines.py,sha256=p9YWpvxpkrlQ2WHgU-48utBsCFF9Jf1vfSVVVV5VJ3U,1300
6
+ pytest_eo/bodies.py,sha256=9cWFgtMkjX_MxrmbdWss1qTcMLHxAJNuf9cfP5KFx2Q,1215
7
+ pytest_eo/bundled.py,sha256=uo_-nFVL-hhV-Ok3-dmEJpOAD770mIz6sSTw253-y3o,528
8
+ pytest_eo/callee.py,sha256=pQ7MrdQFRfo4unelaRYVy12xsJ1zlf-xEH9Cf4EInFM,682
9
+ pytest_eo/capacity.py,sha256=s1rXY3NmRRAzGkN-JiUlAwZuaVppTZZkPkq-sp_U5c8,1459
10
+ pytest_eo/check.py,sha256=Y4Y6fzIeP93P9QajG-XRHcKKxebFxEuFVbAy9IfOzGE,420
11
+ pytest_eo/command.py,sha256=vxiLthNF6l0a0hFhbKVIzZs_UWEdbYBBZKx9mm6az2s,313
12
+ pytest_eo/comment.py,sha256=JGX1_CJbJCFmBqewTb45nmsM5UOWQDEGmpmC1pOgcGM,1110
13
+ pytest_eo/comments.py,sha256=RVbecuThDG_CEmRMpSpagQs1Pgkm_odcJd7izqXEBck,1293
14
+ pytest_eo/edges.py,sha256=ut4IjmmfwtYVodSi2PaEjd6Z46NiP2rRzcn16Aethj8,775
15
+ pytest_eo/encapsulation.py,sha256=jtRam6UpQ8mo2dR3McpUPlPDwS6T7upw1DpT1d1aVng,984
16
+ pytest_eo/ending.py,sha256=anvKCy0leLYtkbe-8Dho8MI1j1SqD5kgvdzJ3NfRwII,910
17
+ pytest_eo/eo_item.py,sha256=AIqmLvyi0tvW8wID4-RbrJIb4Hf2JYZXRjUTfdBbIwE,1368
18
+ pytest_eo/eo_table.py,sha256=91IlQVyVqwWa_RkF-vOXepdgFSAECuFNflIa3Gwr6cs,1028
19
+ pytest_eo/excludes.py,sha256=9hVp_LHrnaF1Kte87WqQpn9CpG3Hj06kr6i0_8M6P2o,715
20
+ pytest_eo/extension.py,sha256=fixHQ6qmAMoON2jVYoJiy_A7T2t8xo7QKdkD6T9AKjg,359
21
+ pytest_eo/fields.py,sha256=jWsu_IVQ-SXjdDuVtyPlUJZEyzUuLs5KYoGZY1iXaxg,1387
22
+ pytest_eo/files.py,sha256=Oaun13ttiKk76sA7xTuPKGjvC2MonXgujhVJfUX4OSw,297
23
+ pytest_eo/final_assertion.py,sha256=Qqp_hHSnq5LHUUJAQiAgsCJgNAz9xM1PM6M0uiMvS90,1393
24
+ pytest_eo/flake_check.py,sha256=ABX_2GpYiPuwp895mltXNkKBmJq1UrqKA6liBiknXaE,1468
25
+ pytest_eo/heading.py,sha256=luAGlGj0fIS44TCWaoLKXMY4A6-CporthWRZAuBhMiM,914
26
+ pytest_eo/inline_comments.py,sha256=uvYzQdkUxioWFKwOCtgakhXhWCRweteeTMB8U4RED8k,1218
27
+ pytest_eo/layout.py,sha256=l22k1o4WKVP8siY7Cyx-oJ4MsvnNOna6j61tntZ8iKo,390
28
+ pytest_eo/margin.py,sha256=8yRlJf0grWr-BwUXjhKSAylyXeUeUcqb9p9OjeDRbhg,1364
29
+ pytest_eo/message.py,sha256=v_Q6HE-E1wpMf8ctgdcTzh5gp6j7G7KJxgeaVxvL8gk,1833
30
+ pytest_eo/module_constants.py,sha256=8nWaVyC34QAry9uZHMzlcsaMD9EpRmRcW2x5e6p5nXY,876
31
+ pytest_eo/mypy_check.py,sha256=w3nHQ-aZhh63OEfwzpux4s7A_hwDaiTa7dndt0L3nOs,1234
32
+ pytest_eo/narrow_suppressions.py,sha256=SgKNNJN3TPuJ60lfPmO9WWQY5VlZWDhS1CE6ppkU1Eg,2174
33
+ pytest_eo/negative_messages.py,sha256=0kt213fA0EYXJf3yz5ZpMuBYCC-7J0iguva7gZWi8ZI,1092
34
+ pytest_eo/output.py,sha256=31qAit03k7GKZhuSGu7-nG_DNEihHdwA3GO5-2cfR2M,1095
35
+ pytest_eo/owned.py,sha256=Y0WazRWj3Yqki2kQh_eK5F6Sza97QhFEOxL71uxT6CM,1016
36
+ pytest_eo/place.py,sha256=B0G-d0_qroVJ2VGtUe8OJ72lIHQa8k9U6zaK4RognKw,476
37
+ pytest_eo/plugin.py,sha256=EVnyOfAu-NaTqEYt2jDZKwOd67Kyumg8C5uUiK0QFxw,1160
38
+ pytest_eo/printout.py,sha256=rG3T4kZmFnyJ8NvRqVwQhxJm08dSto-0SNh6ALTBuYM,577
39
+ pytest_eo/profile.py,sha256=IFOi6lYL0bDOLqNbpRmqk4LN0OvvURYkGseyO1WWItE,320
40
+ pytest_eo/project.py,sha256=lbjBFFKo0m8E70pHY9HU_10wleJoAGvNPJK23DwnEVg,955
41
+ pytest_eo/project_files.py,sha256=td42X4YOJs9UlhfwuuBBvOMobaiL6YlmVWiqFRf1zjg,1062
42
+ pytest_eo/python_module.py,sha256=_WllphQs5kXrc00JJnEMFFCgfibHwLtB0CVCMsLoLOw,1130
43
+ pytest_eo/raising.py,sha256=GmYtDzpCOQ5_KFc3DF4c3LnJddWRQUUB13Lz-lRWn-4,830
44
+ pytest_eo/remark.py,sha256=iIYyooDxx3cQqunBPaa2FUz3dhee04rjRgUHGMXWBfo,662
45
+ pytest_eo/ruff_check.py,sha256=C0OuzPxSRbj35WtYU0lWLF_d_AMU53WDpL4X3YYKMzQ,1466
46
+ pytest_eo/ruff_format_check.py,sha256=sMNsRByys0uOoVZZbqFx-FmpcBQLSJyhFT-b88Ynfcw,1340
47
+ pytest_eo/rule.py,sha256=0orHF-iiQN67i8uhTKxgexzSYO6VxaGqZzShQO7fSNE,371
48
+ pytest_eo/shared.py,sha256=iMsGEjtR_S8t6ZM_mLbbEefQMirecoEaWElpnngyuO8,1182
49
+ pytest_eo/span.py,sha256=5bqUarD2Kt5LN3pUjIedz-fk2JEvTgggEinVJnSRUUs,938
50
+ pytest_eo/strict.py,sha256=u7eEN1RnRqRriO2Ww-AyWCnxWxkag5PwD0IPapDqy4I,3489
51
+ pytest_eo/style_violations_error.py,sha256=Fz771ldN3Lpe-h3-kHMGdcfwmOFcX6ft16rnR9RoKJA,459
52
+ pytest_eo/suppression_check.py,sha256=l1k-xPDX_HftKKDJu3IU1jdnpkqZ0DmHKc7GQw0qolU,1389
53
+ pytest_eo/table.py,sha256=80-QnqaqBex_vWL_YvQXGQOL7voBfdJzkJPCFCyQZkg,304
54
+ pytest_eo/text.py,sha256=67ZYl_CvJ6KkbPHuNoC64D56c77zY6_zAorpRSvPFgk,887
55
+ pytest_eo/tool_failed_error.py,sha256=zKC7XtmckGG-kKgiZ4JCLkQEcjIRI4ChimTbD3l00BY,468
56
+ pytest_eo/trial.py,sha256=Z3BSMwYsg1wsRmxX0hZgnf_33NlZAYjlT4vDMwa6Rsc,1160
57
+ pytest_eo/verified.py,sha256=ViAUWHbenW_BTgEORFVcCwLBQBnwQkMIksB44QwDkB4,784
58
+ pytest_eo/violation.py,sha256=JPgNLcHulIqCOT3hMu2c-GGr-UTk5vj-xR8exUk8Lh0,578
59
+ pytest_eo/wide_suppressions.py,sha256=5qyiW8ioQmv2BTXf4dlrYP2I3uvyA7v2rLr_2KCozms,1367
60
+ pytest_eo/config/flake8.ini,sha256=7AEG-U6qzQW2xl7FXHnmXgTDu0q9TIlgiP8SFEPz6Kg,1293
61
+ pytest_eo/config/mypy.ini,sha256=dyih1DDGQx2NnTyVkdk2zEL9uf8LnlYA2hH9uIt-QtE,98
62
+ pytest_eo/config/ruff.toml,sha256=aF6-MM1bUVd7PE_3mm53pj0U2XJXhbY0taxeYlU_BKU,1549
63
+ pytest_eo-0.5.1.dist-info/METADATA,sha256=n_6gtwD0dB5nA1EVL-GdM9lOlm-AVKG-rWuWat_JGXE,7969
64
+ pytest_eo-0.5.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
65
+ pytest_eo-0.5.1.dist-info/entry_points.txt,sha256=kz2JTYjRLRHI0I5pddKFDjgNY2odA956kHiq-HqqD0Y,166
66
+ pytest_eo-0.5.1.dist-info/licenses/LICENSE.txt,sha256=6v7Og1usuuSqeiAL4u3DlOFfkGza0C98bFZFHKFWCO4,1063
67
+ pytest_eo-0.5.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,7 @@
1
+ [flake8.extension]
2
+ BOD = pytest_eo.bodies:Bodies
3
+ ENC = pytest_eo.encapsulation:Encapsulation
4
+ TST = pytest_eo.angry_tests:AngryTests
5
+
6
+ [pytest11]
7
+ eo = pytest_eo.plugin
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RaulIQ
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.