sarj-python-lint 0.67.7__tar.gz → 0.67.8__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.
Files changed (99) hide show
  1. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/fastapi_openapi_contract.py +9 -29
  4. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/.gitignore +0 -0
  5. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/LICENSE +0 -0
  6. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/README.md +0 -0
  7. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/__init__.py +0 -0
  8. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/__main__.py +0 -0
  9. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/_filesystem.py +0 -0
  10. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  11. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/_secret_names.py +0 -0
  12. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/_version.py +0 -0
  13. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/py.typed +0 -0
  14. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/ratchet.py +0 -0
  15. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rule_base.py +0 -0
  16. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/__init__.py +0 -0
  17. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  18. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_comments.py +0 -0
  19. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  20. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  21. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_first_party.py +0 -0
  22. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_imports.py +0 -0
  23. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_logging.py +0 -0
  24. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_paths.py +0 -0
  25. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  26. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_pytest.py +0 -0
  27. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_registry.py +0 -0
  28. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_sql.py +0 -0
  29. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  30. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  31. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/defect_xfail_requires_strict.py +0 -0
  32. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  33. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  34. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
  35. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
  36. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
  37. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  38. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
  39. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  40. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
  41. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  42. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  43. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  44. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
  45. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
  46. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  47. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  48. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
  49. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  50. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  51. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
  52. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
  53. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  54. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
  55. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
  56. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  57. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  58. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  59. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  60. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
  61. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
  62. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  63. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  64. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  65. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  66. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  67. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  68. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  69. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  70. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  71. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  72. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  73. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  74. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  75. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  76. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  77. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  78. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  79. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  80. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  81. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  82. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  83. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  84. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
  85. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  86. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  87. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  88. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  89. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  90. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  91. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  92. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  93. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  94. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  95. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/stepdown.py +0 -0
  96. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  97. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  98. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
  99. {sarj_python_lint-0.67.7 → sarj_python_lint-0.67.8}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sarj-python-lint
3
- Version: 0.67.7
3
+ Version: 0.67.8
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "0.67.7"
3
+ version = "0.67.8"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -66,9 +66,9 @@ class FastapiOpenapiContract(Rule):
66
66
  id: str = "fastapi-openapi-contract"
67
67
  code: str = "SARJ094"
68
68
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
69
- summary="FastAPI operations must publish explicit request, response, and OpenAPI contracts.",
70
- rationale="Complete route metadata keeps generated OpenAPI accurate for clients, validation, and review.",
71
- remediation="Declare route metadata, typed parameters, response schemas, and documented alternate responses.",
69
+ summary="FastAPI operations must publish accurate request, response, and status contracts.",
70
+ rationale="Typed routes and explicit response behavior keep generated OpenAPI accurate without duplicating self-documenting names in prose.",
71
+ remediation="Declare status codes, typed parameters, response schemas, and alternate responses raised directly by the handler.",
72
72
  category=RuleCategory.CORRECTNESS,
73
73
  limitations=(
74
74
  "Hidden routes, WebSocket handlers, tests, generated files, documentation-source examples, and unrelated decorators are excluded.",
@@ -78,7 +78,7 @@ class FastapiOpenapiContract(Rule):
78
78
  examples=(
79
79
  RuleExample(
80
80
  example_id="missing-operation-metadata",
81
- title="Visible operation without required metadata",
81
+ title="Visible operation without an explicit success status",
82
82
  outcome=ExampleOutcome.MATCH,
83
83
  files=(
84
84
  ExampleFile.python(
@@ -97,7 +97,7 @@ class FastapiOpenapiContract(Rule):
97
97
  files=(
98
98
  ExampleFile.python(
99
99
  "api.py",
100
- "from fastapi import APIRouter\n\nrouter = APIRouter()\n\n@router.get('/users', summary='Read users', description='Returns visible users.', status_code=200)\nasync def users() -> list[UserResponse]:\n return []\n",
100
+ "from fastapi import APIRouter\n\nrouter = APIRouter()\n\n@router.get('/users', status_code=200)\nasync def read_users() -> list[UserResponse]:\n return []\n",
101
101
  ),
102
102
  ),
103
103
  focus_path=PurePosixPath("api.py"),
@@ -134,7 +134,7 @@ class FastapiOpenapiContract(Rule):
134
134
  findings.extend(_check_raw_request(function, routes, index))
135
135
  findings.extend(_check_direct_responses(function, routes, index))
136
136
  for route in _operations(routes):
137
- findings.extend(_check_metadata(function, route))
137
+ findings.extend(_check_metadata(route))
138
138
  findings.extend(_check_projection(route))
139
139
  findings.extend(_check_route_conflicts(declared))
140
140
  return [
@@ -155,29 +155,13 @@ def _operations(routes: tuple[Route, ...]) -> tuple[Route, ...]:
155
155
  return tuple({id(route.decorator): route for route in routes}.values())
156
156
 
157
157
 
158
- def _check_metadata(function: ast.FunctionDef | ast.AsyncFunctionDef, route: Route) -> list[_Finding]:
158
+ def _check_metadata(route: Route) -> list[_Finding]:
159
159
  if route.has_unpack:
160
160
  return []
161
161
  keywords = route.keywords
162
- missing: list[str] = []
163
- if not _nonblank(keywords.get("summary")):
164
- missing.append("summary")
165
- docstring = ast.get_docstring(function, clean=False)
166
- if not (_nonblank(keywords.get("description")) or bool(docstring and docstring.strip())):
167
- missing.append("description or handler docstring")
168
- if "status_code" not in keywords or _literal_none(keywords["status_code"]):
169
- missing.append("status_code")
170
- if not missing:
162
+ if "status_code" in keywords and not _literal_none(keywords["status_code"]):
171
163
  return []
172
- return [_Finding(route.decorator, f"[metadata] operation requires explicit {', '.join(missing)}.")]
173
-
174
-
175
- def _nonblank(node: ast.expr | None) -> bool:
176
- if node is None:
177
- return False
178
- if isinstance(node, ast.Constant):
179
- return isinstance(node.value, str) and bool(node.value.strip())
180
- return True
164
+ return [_Finding(route.decorator, "[metadata] operation requires explicit status_code.")]
181
165
 
182
166
 
183
167
  def _literal_none(node: ast.expr) -> bool:
@@ -238,10 +222,6 @@ def _check_parameters(
238
222
  findings.append(
239
223
  _Finding(parameter, f"[parameter] `{parameter.arg}` must not duplicate FastAPI marker metadata.")
240
224
  )
241
- if marker_name in SCHEMA_MARKERS and not _nonblank(_keyword(marker_call, "description")):
242
- findings.append(
243
- _Finding(parameter, f"[parameter] `{parameter.arg}` marker requires a non-empty description.")
244
- )
245
225
  if marker_name in SCHEMA_MARKERS and _schema_erasing(value_type, index):
246
226
  findings.append(
247
227
  _Finding(