sarj-python-lint 0.87.2__tar.gz → 0.87.4__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 (143) hide show
  1. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/__main__.py +6 -11
  4. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_ratchet_cli.py +2 -2
  5. sarj_python_lint-0.87.4/src/sarj_python_lint/json_boundary.py +10 -0
  6. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/ratchet.py +6 -9
  7. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rule_base.py +3 -0
  8. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +1 -0
  9. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/fakes_in_shared_location.py +1 -0
  10. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +1 -0
  11. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/iac_source_coupled_test.py +1 -0
  12. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/mock_without_spec.py +1 -0
  13. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/named_record_at_boundaries.py +2 -0
  14. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +1 -0
  15. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +1 -0
  16. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_any_mapping_types.py +2 -0
  17. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +2 -0
  18. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +1 -0
  19. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_redundant_literal_description.py +1 -0
  20. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +2 -0
  21. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_restated_comment.py +1 -0
  22. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_typed_doc_sections.py +1 -0
  23. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_unused_value_marker.py +1 -0
  24. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +1 -0
  25. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +1 -0
  26. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/over_mocked_test.py +1 -0
  27. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +2 -0
  28. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +1 -0
  29. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +1 -0
  30. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +1 -0
  31. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +1 -0
  32. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_library_fake.py +1 -0
  33. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +1 -0
  34. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +1 -0
  35. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_module_level_constant.py +1 -0
  36. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +1 -0
  37. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +1 -0
  38. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_or_pattern.py +1 -0
  39. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +2 -0
  40. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +1 -0
  41. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +2 -0
  42. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +1 -0
  43. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +2 -0
  44. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +2 -0
  45. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +2 -0
  46. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +1 -0
  47. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/redundant_docstring.py +1 -0
  48. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +1 -0
  49. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -0
  50. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -0
  51. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_port_for_service.py +1 -0
  52. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +1 -0
  53. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +1 -0
  54. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +1 -0
  55. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +1 -0
  56. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +1 -0
  57. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/typed_error_reasons.py +1 -0
  58. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/unused_test_factory_option.py +1 -0
  59. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/.gitignore +0 -0
  60. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/LICENSE +0 -0
  61. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/README.md +0 -0
  62. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/__init__.py +0 -0
  63. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_analysis_session.py +0 -0
  64. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_filesystem.py +0 -0
  65. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_python_target.py +0 -0
  66. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_secret_names.py +0 -0
  67. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/_version.py +0 -0
  68. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/py.typed +0 -0
  69. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/__init__.py +0 -0
  70. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  71. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  72. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  73. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_comments.py +0 -0
  74. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  75. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  76. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_first_party.py +0 -0
  77. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  78. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_imports.py +0 -0
  79. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_logging.py +0 -0
  80. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_paths.py +0 -0
  81. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_project_index.py +0 -0
  82. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  83. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_pytest.py +0 -0
  84. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_registry.py +0 -0
  85. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_sql.py +0 -0
  86. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  87. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  88. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  89. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  90. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  91. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  92. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  93. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  94. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  95. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  96. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  97. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  98. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  99. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  100. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  101. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  102. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  103. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  104. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  105. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  106. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  107. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  108. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  109. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  110. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  111. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  112. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  113. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  114. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  115. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  116. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  117. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  118. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  119. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  120. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  121. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  122. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  123. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  124. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  125. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  126. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  127. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  128. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  129. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  130. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  131. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  132. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  133. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  134. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  135. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  136. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  137. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  138. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/require_typed_http_test_response.py +0 -0
  139. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  140. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/stepdown.py +0 -0
  141. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  142. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  143. {sarj_python_lint-0.87.2 → sarj_python_lint-0.87.4}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 0.87.2
3
+ Version: 0.87.4
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.87.2"
3
+ version = "0.87.4"
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" }]
@@ -14,6 +14,7 @@ import typer
14
14
  from sarj_python_lint import __version__
15
15
  from sarj_python_lint._analysis_session import AnalysisSession
16
16
  from sarj_python_lint._filesystem import atomic_write_text
17
+ from sarj_python_lint.json_boundary import is_object_mapping, parse_json
17
18
  from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity, is_suppressed
18
19
  from sarj_python_lint.rules import REGISTRY
19
20
  from sarj_python_lint.rules._paths import clear_path_caches
@@ -335,20 +336,14 @@ def _baseline_path(path: Path, *, root: Path | None = None) -> str:
335
336
 
336
337
 
337
338
  def _read_baseline(path: Path) -> dict[str, dict[str, int]]:
338
- raw: object = json.loads( # pyright: ignore[reportAny] — json.loads is an untyped stdlib boundary; the shape is narrowed below
339
- path.read_text(encoding="utf-8")
340
- )
341
- if not isinstance(raw, dict):
339
+ raw = parse_json(path.read_text(encoding="utf-8"))
340
+ if not is_object_mapping(raw):
342
341
  return {}
343
342
  counts: dict[str, dict[str, int]] = {}
344
- for file_key, per_code in raw.items(): # pyright: ignore[reportUnknownVariableType] — json.loads yields Any leaves
345
- if not isinstance(file_key, str) or not isinstance(per_code, dict):
343
+ for file_key, per_code in raw.items():
344
+ if not isinstance(file_key, str) or not is_object_mapping(per_code):
346
345
  continue
347
- counts[file_key] = {
348
- code: n
349
- for code, n in per_code.items() # pyright: ignore[reportUnknownVariableType] — same
350
- if isinstance(code, str) and isinstance(n, int)
351
- }
346
+ counts[file_key] = {code: n for code, n in per_code.items() if isinstance(code, str) and isinstance(n, int)}
352
347
  return counts
353
348
 
354
349
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- import json
5
4
  from pathlib import Path
6
5
  import subprocess # ruff: ignore[suspicious-subprocess-import] -- fixed local Ruff introspection only
7
6
  import sys
@@ -11,6 +10,7 @@ import typer
11
10
 
12
11
  from sarj_python_lint._filesystem import atomic_write_text
13
12
  from sarj_python_lint._version import __version__
13
+ from sarj_python_lint.json_boundary import parse_json
14
14
  from sarj_python_lint.ratchet import (
15
15
  DEFAULT_PER_FILE_CEILING,
16
16
  Baseline,
@@ -142,7 +142,7 @@ def _ruff_selector_aliases() -> dict[str, str]:
142
142
  encoding="utf-8",
143
143
  text=True,
144
144
  )
145
- raw: object = json.loads(completed.stdout) # pyright: ignore[reportAny] -- validated below
145
+ raw = parse_json(completed.stdout)
146
146
  if not _is_object_list(raw):
147
147
  msg = "Ruff rule catalog was not a JSON list"
148
148
  raise TypeError(msg)
@@ -0,0 +1,10 @@
1
+ import json
2
+ from typing import TypeIs
3
+
4
+
5
+ def parse_json(text: str) -> object:
6
+ return json.loads(text) # pyright: ignore[reportAny] -- untyped stdlib parser boundary.
7
+
8
+
9
+ def is_object_mapping(value: object) -> TypeIs[dict[object, object]]:
10
+ return isinstance(value, dict)
@@ -9,6 +9,8 @@ import tokenize
9
9
  from types import MappingProxyType
10
10
  from typing import TYPE_CHECKING, Final, NamedTuple, TypeGuard
11
11
 
12
+ from sarj_python_lint.json_boundary import is_object_mapping, parse_json
13
+
12
14
 
13
15
  if TYPE_CHECKING:
14
16
  from collections.abc import Iterable, Iterator, Mapping
@@ -216,9 +218,7 @@ def seed(measurement: Measurement, baseline: Baseline) -> Baseline:
216
218
 
217
219
 
218
220
  def load_baseline(path: Path) -> Baseline:
219
- raw: object = json.loads( # pyright: ignore[reportAny] — json.loads is an untyped stdlib boundary; every read below narrows
220
- path.read_text(encoding="utf-8")
221
- )
221
+ raw = parse_json(path.read_text(encoding="utf-8"))
222
222
  schema = _get(raw, "schema_version")
223
223
  if schema is not None and schema != BASELINE_SCHEMA_VERSION:
224
224
  msg = f"unsupported suppression baseline schema_version: {schema!r}"
@@ -238,9 +238,9 @@ def load_baseline(path: Path) -> Baseline:
238
238
 
239
239
 
240
240
  def _get(mapping: object, key: str) -> object:
241
- if not isinstance(mapping, dict):
241
+ if not is_object_mapping(mapping):
242
242
  return None
243
- return mapping.get(key) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] — json leaves are Any
243
+ return mapping.get(key)
244
244
 
245
245
 
246
246
  def dump_baseline(baseline: Baseline, packages: Iterable[str]) -> str:
@@ -308,10 +308,7 @@ def _is_object_list(value: object) -> TypeGuard[list[object]]:
308
308
 
309
309
 
310
310
  def _is_string_object_mapping(value: object) -> TypeGuard[dict[str, object]]:
311
- return isinstance(value, dict) and all(
312
- isinstance(key, str)
313
- for key in value # pyright: ignore[reportUnknownVariableType] — runtime validation narrows untyped JSON keys.
314
- )
311
+ return is_object_mapping(value) and all(isinstance(key, str) for key in value)
315
312
 
316
313
 
317
314
  def _normalized_subtree(value: str) -> str:
@@ -155,6 +155,7 @@ class RuleDocumentation:
155
155
  rationale: str
156
156
  remediation: str
157
157
  category: RuleCategory
158
+ default_level: Severity = Severity.ERROR
158
159
  autofix: AutofixPolicy = AutofixPolicy.NONE
159
160
  aliases: tuple[str, ...] = ()
160
161
  limitations: tuple[str, ...] = ()
@@ -207,6 +208,7 @@ class NativeRuleSpec:
207
208
  rationale: str
208
209
  remediation: str
209
210
  category: RuleCategory
211
+ default_level: Severity
210
212
  autofix: AutofixPolicy
211
213
  aliases: tuple[str, ...]
212
214
  limitations: tuple[str, ...]
@@ -283,6 +285,7 @@ class Rule(ABC):
283
285
  rationale=authored.rationale,
284
286
  remediation=authored.remediation,
285
287
  category=authored.category,
288
+ default_level=authored.default_level,
286
289
  autofix=authored.autofix,
287
290
  aliases=authored.aliases,
288
291
  limitations=authored.limitations,
@@ -264,6 +264,7 @@ class ComplexPostgresQueryRequiresArchitectureReview(Rule):
264
264
  id: str = "complex-postgres-query-requires-architecture-review"
265
265
  code: str = "SARJ437"
266
266
  documentation = RuleDocumentation(
267
+ default_level=Severity.WARNING,
267
268
  summary="Complex executable PostgreSQL query shapes require architecture review.",
268
269
  rationale=(
269
270
  "Join-heavy, deeply staged, or wide joined reads can obscure cardinality, bounds, ordering, and locking "
@@ -50,6 +50,7 @@ class FakesInSharedLocation(Rule):
50
50
  id = "fakes-in-shared-location"
51
51
  code = "SARJ428"
52
52
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
53
+ default_level=Severity.WARNING,
53
54
  summary="Review named top-level test doubles for shared-support ownership unless they are intentionally scenario-local.",
54
55
  rationale=(
55
56
  "Reusable doubles hidden in an individual test module are difficult to discover and are often recreated. "
@@ -73,6 +73,7 @@ class FastapiExplicitOpenapiContract(Rule):
73
73
  id: str = "fastapi-explicit-openapi-contract"
74
74
  code: str = "SARJ094"
75
75
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
76
+ default_level=Severity.WARNING,
76
77
  summary="Visible FastAPI operations pin locally reviewable metadata and avoid statically provable OpenAPI gaps.",
77
78
  rationale=(
78
79
  "FastAPI can infer valid schemas and default statuses. This stricter organizational policy pins locally "
@@ -32,6 +32,7 @@ class IacSourceCoupledTest(Rule):
32
32
  id = "iac-source-coupled-test"
33
33
  code = "SARJ412"
34
34
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
35
+ default_level=Severity.WARNING,
35
36
  summary="Test uses raw Terraform/HCL text as an infrastructure-behavior oracle.",
36
37
  rationale=(
37
38
  "Substring and regex checks can pass on comments, formatting, or unreachable Terraform configuration "
@@ -121,6 +121,7 @@ class MockWithoutSpec(Rule):
121
121
  id: str = "mock-without-spec"
122
122
  code: str = "SARJ040"
123
123
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
124
+ default_level=Severity.WARNING,
124
125
  summary="Unrestricted mock permits attributes outside the collaborator contract.",
125
126
  rationale=(
126
127
  "A mock without a spec permits removed or misspelled attributes; a callable mock without autospec may "
@@ -13,6 +13,7 @@ from sarj_python_lint.rule_base import (
13
13
  RuleCategory,
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
+ Severity,
16
17
  is_suppressed,
17
18
  parse_or_none,
18
19
  )
@@ -42,6 +43,7 @@ class NamedRecordAtBoundaries(Rule):
42
43
  id: str = "named-record-at-boundaries"
43
44
  code: str = "SARJ008"
44
45
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
46
+ default_level=Severity.WARNING,
45
47
  summary="Public Python API returns an unnamed fixed-shape record.",
46
48
  rationale="A named record makes field types and required keys explicit to callers and static tooling.",
47
49
  remediation="Define and return a `TypedDict`, Pydantic model, or frozen dataclass for the fixed record shape.",
@@ -204,6 +204,7 @@ class NegativeOnlyHttpStatusAssertion(Rule):
204
204
  id = "negative-only-http-status-assertion"
205
205
  code = "SARJ408"
206
206
  documentation = RuleDocumentation(
207
+ default_level=Severity.WARNING,
207
208
  summary="HTTP test assertion only excludes a server error instead of identifying the intended response.",
208
209
  rationale=(
209
210
  "Authentication, routing, validation, and domain failures can all replace the intended response while "
@@ -152,6 +152,7 @@ class NoAnalyticalAggregationInPostgresStore(Rule):
152
152
  id: str = "no-analytical-aggregation-in-postgres-store"
153
153
  code: str = "SARJ020"
154
154
  documentation = RuleDocumentation(
155
+ default_level=Severity.WARNING,
155
156
  summary="Potentially analytical PostgreSQL store queries require review.",
156
157
  rationale=(
157
158
  "Unbounded reporting and statistical scans can compete with transactional reads. "
@@ -13,6 +13,7 @@ from sarj_python_lint.rule_base import (
13
13
  RuleCategory,
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
+ Severity,
16
17
  is_suppressed,
17
18
  )
18
19
  from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
@@ -37,6 +38,7 @@ class NoAnyMappingTypes(Rule):
37
38
  id = "no-any-mapping-types"
38
39
  code = "SARJ447"
39
40
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
41
+ default_level=Severity.WARNING,
40
42
  summary="String-keyed mapping types must not erase their values with `Any`.",
41
43
  rationale=(
42
44
  "An `Any`-valued mapping disables checking transitively and conceals whether the value is a fixed record, "
@@ -13,6 +13,7 @@ from sarj_python_lint.rule_base import (
13
13
  RuleCategory,
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
+ Severity,
16
17
  parse_or_none,
17
18
  )
18
19
  from sarj_python_lint.rules._paths import is_generated, is_test_path, is_test_support_path
@@ -30,6 +31,7 @@ class NoRawConnectionInTests(Rule):
30
31
  id = "no-raw-connection-in-tests"
31
32
  code = "SARJ429"
32
33
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
34
+ default_level=Severity.WARNING,
33
35
  summary="Do not acquire raw database connections in tests.",
34
36
  rationale=(
35
37
  "Tests that reach through a pool couple assertions and setup to persistence internals, bypass the "
@@ -112,6 +112,7 @@ class NoRawSourceTextTestOracle(Rule):
112
112
  id = "no-raw-source-text-test-oracle"
113
113
  code = "SARJ402"
114
114
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
115
+ default_level=Severity.WARNING,
115
116
  summary="Test uses raw text from a source-like project path as its oracle.",
116
117
  rationale="Substring and regex checks can pass on comments or unreachable configuration and fail after behavior-preserving formatting changes.",
117
118
  remediation=(
@@ -44,6 +44,7 @@ class NoRedundantLiteralDescription(Rule):
44
44
  id = "no-restated-closed-domain-description"
45
45
  code = "SARJ423"
46
46
  documentation = RuleDocumentation(
47
+ default_level=Severity.WARNING,
47
48
  summary="Do not restate a string Literal or local string Enum domain in its Pydantic description.",
48
49
  rationale=(
49
50
  "Pydantic already publishes literal and enum domains in JSON Schema; duplicate must-be prose can "
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
14
14
  RuleCategory,
15
15
  RuleDocumentation,
16
16
  RuleExample,
17
+ Severity,
17
18
  parse_or_none,
18
19
  )
19
20
  from sarj_python_lint.rules._imports import ImportIndex
@@ -33,6 +34,7 @@ class NoRedundantModuleAliasExports(Rule):
33
34
  id: str = "no-redundant-module-alias-exports"
34
35
  code: str = "SARJ440"
35
36
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
37
+ default_level=Severity.WARNING,
36
38
  summary="Do not manufacture public APIs from private names or replace the current module.",
37
39
  rationale=(
38
40
  "Binding a public name to a private implementation creates two names for one API and disguises the "
@@ -253,6 +253,7 @@ class NoRestatedComment(Rule):
253
253
  id: str = "no-restated-comment"
254
254
  code: str = "SARJ049"
255
255
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
256
+ default_level=Severity.WARNING,
256
257
  summary="Short standalone comment lexically restates the immediately following simple action.",
257
258
  rationale=(
258
259
  "When a comment adds no rationale, scope, ordering, or constraint, it duplicates code and can become stale. "
@@ -28,6 +28,7 @@ class NoTypedDocSections(Rule):
28
28
  id = "no-docstring-type-restatement"
29
29
  code = "SARJ092"
30
30
  documentation = RuleDocumentation(
31
+ default_level=Severity.WARNING,
31
32
  summary="A docstring type label repeats an annotation from the fully typed signature.",
32
33
  rationale=(
33
34
  "A repeated type spelling can drift from the annotation and obscures the behavioral contract that only prose "
@@ -29,6 +29,7 @@ class NoUnusedValueMarker(Rule):
29
29
  id = "no-unused-value-marker"
30
30
  code = "SARJ452"
31
31
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
32
+ default_level=Severity.WARNING,
32
33
  summary="Do not use standalone assignments to `_` to discard values.",
33
34
  rationale=(
34
35
  "A standalone `_ = value` assignment adds a binding without clarifying intent. A side-effecting call can "
@@ -114,6 +114,7 @@ class NoWholeRequestResponsePayloadInLog(Rule):
114
114
  id: str = "no-whole-request-response-payload-in-log"
115
115
  code: str = "SARJ436"
116
116
  documentation = RuleDocumentation(
117
+ default_level=Severity.WARNING,
117
118
  summary="Whole request or response payloads passed to logging calls require review.",
118
119
  rationale=(
119
120
  "Request and response bodies often contain personal, financial, or echoed authentication data, while "
@@ -56,6 +56,7 @@ class OpaqueParametrizeCaseNeedsId(Rule):
56
56
  id: str = "opaque-parametrize-case-needs-id"
57
57
  code: str = "SARJ042"
58
58
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
59
+ default_level=Severity.WARNING,
59
60
  summary="Opaque static pytest parameter cases rely on argument-name-and-index fallback IDs.",
60
61
  rationale="Fallback IDs such as `payload0` are hard to diagnose and silently change when the table is reordered.",
61
62
  remediation=(
@@ -179,6 +179,7 @@ class OverMockedTest(Rule):
179
179
  id: str = "over-mocked-test"
180
180
  code: str = "SARJ062"
181
181
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
182
+ default_level=Severity.WARNING,
182
183
  summary="Tests should not use more than five independently rooted test doubles or collaborator substitutions.",
183
184
  rationale="Broad double setup obscures the behavior under test and often couples tests to implementation wiring.",
184
185
  remediation=(
@@ -18,6 +18,7 @@ from sarj_python_lint.rule_base import (
18
18
  RuleCategory,
19
19
  RuleDocumentation,
20
20
  RuleExample,
21
+ Severity,
21
22
  is_suppressed,
22
23
  parse_or_none,
23
24
  )
@@ -77,6 +78,7 @@ class PreferCollectionComprehension(Rule):
77
78
  id = "prefer-collection-comprehension"
78
79
  code = "SARJ430"
79
80
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
81
+ default_level=Severity.WARNING,
80
82
  summary="Single-purpose fresh collection builder loop — prefer a direct comprehension.",
81
83
  rationale=(
82
84
  "An empty collection followed by a loop whose only behavior is one projection or filtered insertion "
@@ -94,6 +94,7 @@ class PreferConstantTimeSecretCompare(Rule):
94
94
  id: str = "prefer-constant-time-secret-compare"
95
95
  code: str = "SARJ011"
96
96
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
97
+ default_level=Severity.WARNING,
97
98
  summary="Externally supplied authenticators are compared with timing-sensitive equality.",
98
99
  rationale=(
99
100
  "Ordinary equality may short-circuit based on matching content when attacker-controlled credentials are "
@@ -120,6 +120,7 @@ class PreferFstringOverConcat(Rule):
120
120
  id: str = "prefer-fstring-over-concat"
121
121
  code: str = "SARJ068"
122
122
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
123
+ default_level=Severity.WARNING,
123
124
  summary="Prefer f-strings for short human-readable interpolation when they make the result clearer.",
124
125
  rationale="For prose-like strings, f-strings keep interpolated values and surrounding text visible together.",
125
126
  remediation=(
@@ -73,6 +73,7 @@ class PreferImmutableModuleConstant(Rule):
73
73
  id: str = "prefer-immutable-module-constant"
74
74
  code: str = "SARJ096"
75
75
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
76
+ default_level=Severity.WARNING,
76
77
  summary=("Nonempty uppercase module collections allow top-level membership or keys to change at runtime."),
77
78
  rationale=(
78
79
  "A constant-looking collection can expose process-wide top-level mutation even when callers intend it as a "
@@ -37,6 +37,7 @@ class PreferInjectedDependencyOverMonkeypatch(Rule):
37
37
  id: str = "prefer-injected-dependency-over-monkeypatch"
38
38
  code: str = "SARJ445"
39
39
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
40
+ default_level=Severity.WARNING,
40
41
  summary="Tests should inject dependencies instead of replacing attributes through ambient patching.",
41
42
  rationale=(
42
43
  "Attribute patching hides collaborators and configuration behind ambient module or object state, coupling "
@@ -341,6 +341,7 @@ class PreferLibraryFake(Rule):
341
341
  id: str = "prefer-library-fake"
342
342
  code: str = "SARJ059"
343
343
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
344
+ default_level=Severity.WARNING,
344
345
  summary="Prefer a maintained fake, emulator, recorder, or test service for substantial third-party protocols.",
345
346
  rationale=(
346
347
  "A substantial hand-written protocol model can drift from supported request, state, and error behavior."
@@ -86,6 +86,7 @@ class PreferMatchExceptionDispatch(Rule):
86
86
  id: str = "prefer-match-exception-dispatch"
87
87
  code: str = "SARJ453"
88
88
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
89
+ default_level=Severity.WARNING,
89
90
  summary="Prefer guarded match/case for refined exception type dispatch.",
90
91
  rationale=(
91
92
  "A long exception classifier that repeats isinstance checks and then refines one exception type hides "
@@ -38,6 +38,7 @@ class PreferMatchValueDispatch(Rule):
38
38
  id: str = "prefer-match-value-dispatch"
39
39
  code: str = "SARJ439"
40
40
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
41
+ default_level=Severity.WARNING,
41
42
  summary="Prefer match/case for repeated dispatch on one value with a fallback.",
42
43
  rationale="A single dispatch subject makes distinct value cases and their fallback easier to review.",
43
44
  remediation=(
@@ -96,6 +96,7 @@ class PreferModuleLevelConstant(Rule):
96
96
  id: str = "prefer-module-level-constant"
97
97
  code: str = "SARJ039"
98
98
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
99
+ default_level=Severity.WARNING,
99
100
  summary="Hoist repeatedly read static values when an immutable module representation preserves behavior.",
100
101
  rationale=(
101
102
  "Rebuilding a substantial static collection repeats allocation, while calling `re.compile` with a "
@@ -44,6 +44,7 @@ class PreferMonkeypatchForProcessStateInTest(Rule):
44
44
  id = "prefer-monkeypatch-for-process-state-in-test"
45
45
  code = "SARJ446"
46
46
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
47
+ default_level=Severity.WARNING,
47
48
  summary="Test mutates process-wide state without a restoring test scope.",
48
49
  rationale=(
49
50
  "The working directory and Python import registries are shared by every test in the process. A direct "
@@ -40,6 +40,7 @@ class PreferNonNullableCollection(Rule):
40
40
  id: str = "prefer-non-nullable-collection"
41
41
  code: str = "SARJ082"
42
42
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
43
+ default_level=Severity.WARNING,
43
44
  summary="Avoid nullable list parameters that are immediately collapsed to an empty list.",
44
45
  rationale=(
45
46
  "When an implementation immediately replaces both `None` and an empty list with the same fresh empty list, "
@@ -39,6 +39,7 @@ class PreferOrPattern(Rule):
39
39
  id: str = "prefer-or-pattern"
40
40
  code: str = "SARJ070"
41
41
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
42
+ default_level=Severity.WARNING,
42
43
  summary="Merge adjacent `case` arms with identical bodies into one or-pattern.",
43
44
  rationale="An or-pattern expresses shared handling once and prevents identical arms from drifting apart.",
44
45
  remediation="Join the equivalent patterns with `|` and keep their shared body under the merged arm.",
@@ -19,6 +19,7 @@ from sarj_python_lint.rule_base import (
19
19
  RuleCategory,
20
20
  RuleDocumentation,
21
21
  RuleExample,
22
+ Severity,
22
23
  is_suppressed,
23
24
  parse_or_none,
24
25
  )
@@ -52,6 +53,7 @@ class PreferPydanticJsonValue(Rule):
52
53
  id = "prefer-pydantic-json-value"
53
54
  code = "SARJ454"
54
55
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
56
+ default_level=Severity.WARNING,
55
57
  summary="Recursive JSON value alias duplicates `pydantic.JsonValue`.",
56
58
  rationale=(
57
59
  "Pydantic's canonical JSON value alias keeps the static contract aligned with runtime validation and "
@@ -114,6 +114,7 @@ class PreferSelfTypeAnnotation(Rule):
114
114
  id: str = "prefer-self-type-annotation"
115
115
  code: str = "SARJ078"
116
116
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
117
+ default_level=Severity.WARNING,
117
118
  summary="Prefer `Self` for self-returning methods and alternate constructors.",
118
119
  rationale="`Self` preserves the concrete subclass type when an inherited method returns its receiver or constructs through its class receiver.",
119
120
  remediation="Import `Self` from `typing` (or `typing_extensions` on Python before 3.11) and use it as the return annotation.",
@@ -13,6 +13,7 @@ from sarj_python_lint.rule_base import (
13
13
  RuleCategory,
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
+ Severity,
16
17
  is_suppressed,
17
18
  parse_or_none,
18
19
  )
@@ -46,6 +47,7 @@ class PreferSetIsdisjoint(Rule):
46
47
  id = "prefer-set-isdisjoint"
47
48
  code = "SARJ431"
48
49
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
50
+ default_level=Severity.WARNING,
49
51
  summary="Prefer `set.isdisjoint` when a built-in set intersection is used only as a boolean predicate.",
50
52
  rationale="`isdisjoint` names the overlap predicate directly and avoids allocating an intersection that is immediately discarded.",
51
53
  remediation="Use `left.isdisjoint(right)` and negate it when the condition requires overlap.",
@@ -42,6 +42,7 @@ class PreferWalrusAwaitedNoneGuard(Rule):
42
42
  id = "prefer-walrus-awaited-none-guard"
43
43
  code = "SARJ432"
44
44
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
45
+ default_level=Severity.WARNING,
45
46
  summary="Bind a compact awaited lookup in its immediately following terminal None guard.",
46
47
  rationale=(
47
48
  "When an awaited lookup and its terminal None guard are adjacent, binding in the condition keeps "
@@ -13,6 +13,7 @@ from sarj_python_lint.rule_base import (
13
13
  RuleCategory,
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
+ Severity,
16
17
  is_suppressed,
17
18
  parse_or_none,
18
19
  )
@@ -199,6 +200,7 @@ class PreferWalrusComprehensionFilter(Rule):
199
200
  id: str = "prefer-walrus-comprehension-filter"
200
201
  code: str = "SARJ076"
201
202
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
203
+ default_level=Severity.WARNING,
202
204
  summary="The same call runs in a comprehension filter and its result.",
203
205
  rationale="When both evaluations are intended to produce one stable value, repeating the call wastes work and obscures that relationship.",
204
206
  remediation=(
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
14
14
  RuleCategory,
15
15
  RuleDocumentation,
16
16
  RuleExample,
17
+ Severity,
17
18
  is_suppressed,
18
19
  parse_or_none,
19
20
  )
@@ -58,6 +59,7 @@ class PreferWalrusRegexMatch(Rule):
58
59
  id: str = "prefer-walrus-regex-match"
59
60
  code: str = "SARJ081"
60
61
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
62
+ default_level=Severity.WARNING,
61
63
  summary="A proven regex Match-or-None result is assigned only for the following condition.",
62
64
  rationale="A named expression can keep a short regex operation with its only condition while preserving access to the result.",
63
65
  remediation=(
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
14
14
  RuleCategory,
15
15
  RuleDocumentation,
16
16
  RuleExample,
17
+ Severity,
17
18
  is_suppressed,
18
19
  parse_or_none,
19
20
  )
@@ -30,6 +31,7 @@ class PreferWalrusStreamLoop(Rule):
30
31
  id: str = "prefer-walrus-stream-loop"
31
32
  code: str = "SARJ077"
32
33
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
34
+ default_level=Severity.WARNING,
33
35
  summary="Collapse a compact producer assignment and immediate sentinel break into a named-expression loop.",
34
36
  rationale="A named-expression loop can state a repeated producer call and its termination condition together.",
35
37
  remediation=(
@@ -45,6 +45,7 @@ class PytestFixtureReturnsBareTuple(Rule):
45
45
  id: str = "pytest-fixture-returns-bare-tuple"
46
46
  code: str = "SARJ044"
47
47
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
48
+ default_level=Severity.WARNING,
48
49
  summary="Pytest fixture exposes a fixed positional record as an unnamed tuple.",
49
50
  rationale=(
50
51
  "Tuple-shaped fixture APIs encode each value's role in its position, so call sites are opaque and "
@@ -46,6 +46,7 @@ class RedundantDocstring(Rule):
46
46
  id: str = "redundant-docstring"
47
47
  code: str = "SARJ050"
48
48
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
49
+ default_level=Severity.WARNING,
49
50
  summary="Function or plain-method docstring only repeats its declaration.",
50
51
  rationale="Restating a clear name and signature creates maintenance work without helping callers.",
51
52
  remediation=(
@@ -61,6 +61,7 @@ class RepeatedKwargHeavyCallInTest(Rule):
61
61
  id: str = "repeated-kwarg-heavy-call-in-test"
62
62
  code: str = "SARJ045"
63
63
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
64
+ default_level=Severity.WARNING,
64
65
  summary="Tests repeat at least seven explicit keyword names across calls to the same callee.",
65
66
  rationale=(
66
67
  "Large repeated argument lists duplicate incidental setup, bury scenario differences, and make signature "
@@ -80,6 +80,7 @@ class RepeatedStaticCallCases(Rule):
80
80
  id = "repeated-static-call-cases"
81
81
  code = "SARJ413"
82
82
  documentation = RuleDocumentation(
83
+ default_level=Severity.WARNING,
83
84
  summary="Three same-shape literal call assertions may be independent parameter cases.",
84
85
  rationale=(
85
86
  "When the calls are independent, named parameters isolate failures and identify the input that failed. "
@@ -118,6 +118,7 @@ class RequireKeywordOnlySwapProneParams(Rule):
118
118
  id: str = "require-keyword-only-swap-prone-params"
119
119
  code: str = "SARJ034"
120
120
  documentation = RuleDocumentation(
121
+ default_level=Severity.WARNING,
121
122
  summary="Risky-name positional parameters sharing a primitive annotation may be confused.",
122
123
  rationale="A caller can exchange semantically distinct positional values without a type-checking failure.",
123
124
  remediation=(
@@ -194,6 +194,7 @@ class RequirePortForService(ProjectRule):
194
194
  id: str = "require-port-for-service"
195
195
  code: str = "SARJ071"
196
196
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
197
+ default_level=Severity.WARNING,
197
198
  summary="Consider a consumer-owned port when visible service structure suggests a substitution boundary.",
198
199
  rationale="A small port can decouple consumers when they genuinely need to substitute a concrete service boundary.",
199
200
  remediation=(
@@ -97,6 +97,7 @@ class RequirePydanticForExternalJson(Rule):
97
97
  id = "require-pydantic-for-external-json"
98
98
  code = "SARJ411"
99
99
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
100
+ default_level=Severity.WARNING,
100
101
  summary="Proven external JSON record fields are consumed before runtime schema validation.",
101
102
  rationale=(
102
103
  "Annotations, casts, and partial key checks do not validate a wire protocol; a maintained runtime "
@@ -95,6 +95,7 @@ class RequirePydanticOrdinalLowerBound(Rule):
95
95
  id = "require-pydantic-ordinal-lower-bound"
96
96
  code = "SARJ418"
97
97
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
98
+ default_level=Severity.WARNING,
98
99
  summary="A Pydantic ordinal field maps its first position to N but accepts smaller integers.",
99
100
  rationale=(
100
101
  "An unqualified `N for the first ...` mapping defines the origin of an ordinal field; matching schema "
@@ -95,6 +95,7 @@ class StoreGetDelegatesToBulkRead(Rule):
95
95
  id = "store-get-delegates-to-bulk-read"
96
96
  code = "SARJ421"
97
97
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
98
+ default_level=Severity.WARNING,
98
99
  summary="A store singleton operation should reuse its equivalent bulk implementation.",
99
100
  rationale=(
100
101
  "Independent singleton and bulk queries can drift in filtering, mutation semantics, row conversion, "
@@ -65,6 +65,7 @@ class StoreInsertRequiresOnConflict(Rule):
65
65
  id: str = "replay-contract-insert-requires-duplicate-policy"
66
66
  code: str = "SARJ018"
67
67
  documentation = RuleDocumentation(
68
+ default_level=Severity.WARNING,
68
69
  summary="A literal INSERT in a replay-named store callable must declare duplicate behavior.",
69
70
  rationale=(
70
71
  "A store callable named ensure, enqueue, record_once, get_or_create, create_if_absent, or "
@@ -56,6 +56,7 @@ class TimestampOrderRequiresTiebreaker(Rule):
56
56
  id = "timestamp-order-requires-tiebreaker"
57
57
  code = "SARJ407"
58
58
  documentation = RuleDocumentation(
59
+ default_level=Severity.WARNING,
59
60
  summary=(
60
61
  "Bounded store SQL whose final result-order key looks like a `*_at` timestamp should include a "
61
62
  "deterministic secondary key."
@@ -32,6 +32,7 @@ class TypedErrorReasons(Rule):
32
32
  id = "typed-error-reasons"
33
33
  code = "SARJ435"
34
34
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
35
+ default_level=Severity.WARNING,
35
36
  summary="Review joined exception strings for fixed reason identities versus dynamic context.",
36
37
  rationale=(
37
38
  "Fixed reason identities benefit from stable codes for API clients, UI formatting, telemetry, and "
@@ -35,6 +35,7 @@ class UnusedTestFactoryOption(Rule):
35
35
  id = "unused-test-factory-option"
36
36
  code = "SARJ443"
37
37
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
38
+ default_level=Severity.WARNING,
38
39
  summary="A private test factory exposes a literal option that visible callers never vary.",
39
40
  rationale="Unused customization obscures the values that actually distinguish test scenarios.",
40
41
  remediation="Keep the value in the factory's construction instead of exposing an unused option; retain it if external callers need it.",