sarj-python-lint 1.90.1__tar.gz → 1.92.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.
Files changed (309) hide show
  1. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/__main__.py +2 -1
  4. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_registry.py +12 -0
  5. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/no_generic_json_parser.py +148 -0
  6. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/no_mutating_input_with_result.py +139 -0
  7. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +132 -0
  8. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/no_test_comments.py +127 -0
  9. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +112 -2
  10. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +213 -0
  11. sarj_python_lint-1.92.0/src/sarj_python_lint/rules/require_constrained_api_integer.py +178 -0
  12. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/.gitignore +0 -0
  13. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/LICENSE +0 -0
  14. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/README.md +0 -0
  15. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/__init__.py +0 -0
  16. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  17. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix.py +0 -0
  18. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  19. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  20. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  21. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  22. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  23. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  24. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  25. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  26. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  27. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  28. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  29. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  30. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  31. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  32. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_file_context.py +0 -0
  33. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_filesystem.py +0 -0
  34. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_fixes.py +0 -0
  35. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_parallel.py +0 -0
  36. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
  37. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_python_target.py +0 -0
  38. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  39. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_secret_names.py +0 -0
  40. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_source.py +0 -0
  41. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/_version.py +0 -0
  42. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/application_logging.py +0 -0
  43. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/autofix.py +0 -0
  44. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/benchmark.py +0 -0
  45. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/cast_access.py +0 -0
  46. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  47. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/json_boundary.py +0 -0
  48. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/py.typed +0 -0
  49. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/ratchet.py +0 -0
  50. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rule_base.py +0 -0
  51. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  52. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  53. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  54. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  55. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  56. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  57. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  58. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  59. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  60. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  61. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  62. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  63. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  64. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  65. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  66. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  67. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  68. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  69. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  70. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  71. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  72. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  73. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  74. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  75. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  76. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  77. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  78. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  79. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  80. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  81. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  82. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  83. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  84. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  85. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  86. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
  87. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  88. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  89. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  90. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  91. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  92. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  93. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  94. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  95. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  96. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  97. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  98. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  99. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  100. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  101. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  102. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  103. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  104. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  105. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  106. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  107. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  108. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  109. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  110. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  111. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  112. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  113. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  114. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  115. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  116. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  117. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  118. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  119. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  120. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  121. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  122. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  123. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  124. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  125. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  126. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  127. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  128. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  129. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  130. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  131. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  132. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  133. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  134. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  135. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  136. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  137. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  138. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  139. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  140. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  141. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  142. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  143. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  144. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  145. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  146. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  147. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  148. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  149. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  150. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  151. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  152. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  153. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  154. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  155. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  156. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  157. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  158. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  159. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  160. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  161. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  162. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  163. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  164. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  165. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  166. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  167. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  168. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  169. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  170. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  171. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  172. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  173. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  174. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  175. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  176. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  177. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  178. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  179. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  180. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  181. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  182. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  183. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  184. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  185. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  186. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  187. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  188. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  189. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  190. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  191. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  192. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  193. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  194. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  195. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  196. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  197. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  198. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  199. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  200. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  201. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  202. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  203. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  204. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  205. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  206. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  207. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  208. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  209. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  210. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  211. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  212. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  213. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  214. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  215. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  216. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  217. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  218. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  219. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  220. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  221. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  222. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  223. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  224. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  225. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  226. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  227. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  228. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  229. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  230. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  231. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  232. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  233. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  234. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  235. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  236. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  237. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  238. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  239. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  240. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  241. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  242. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  243. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  244. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  245. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  246. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  247. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  248. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  249. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  250. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  251. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  252. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  253. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  254. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  255. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  256. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  257. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  258. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  259. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  260. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  261. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  262. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  263. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  264. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  265. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  266. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  267. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  268. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  269. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  270. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  271. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  272. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  273. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  274. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  275. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  276. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  277. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  278. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  279. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  280. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  281. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  282. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  283. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  284. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  285. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  286. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  287. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  288. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  289. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  290. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  291. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  292. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  293. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  294. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  295. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  296. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  297. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  298. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  299. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  300. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  301. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  302. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  303. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  304. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  305. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  306. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  307. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  308. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  309. {sarj_python_lint-1.90.1 → sarj_python_lint-1.92.0}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.90.1
3
+ Version: 1.92.0
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 = "1.90.1"
3
+ version = "1.92.0"
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" }]
@@ -233,6 +233,7 @@ _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
233
233
  "SARJ086": frozenset({"SARJ420"}),
234
234
  "SARJ087": frozenset({"SARJ420"}),
235
235
  "SARJ088": frozenset({"SARJ050", "SARJ085", "SARJ420"}),
236
+ "SARJ089": frozenset({"SARJ551"}),
236
237
  "SARJ092": frozenset({"SARJ086", "SARJ087", "SARJ420"}),
237
238
  "SARJ093": frozenset({"SARJ034"}),
238
239
  # Visible FastAPI operations have a route-aware contract diagnostic.
@@ -251,7 +252,7 @@ _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
251
252
  "SARJ447": frozenset({"SARJ008"}),
252
253
  "SARJ450": frozenset({"SARJ521", "SARJ535"}),
253
254
  "SARJ521": frozenset({"SARJ535"}),
254
- "SARJ457": frozenset({"SARJ045"}),
255
+ "SARJ457": frozenset({"SARJ045", "SARJ526"}),
255
256
  "SARJ465": frozenset({"SARJ071"}),
256
257
  "SARJ491": frozenset({"SARJ400"}),
257
258
  }
@@ -61,6 +61,7 @@ from sarj_python_lint.rules.no_fixed_sleep_in_loop import NoFixedSleepInLoop
61
61
  from sarj_python_lint.rules.no_frozen_after_validator_field_write import (
62
62
  NoFrozenAfterValidatorFieldWrite,
63
63
  )
64
+ from sarj_python_lint.rules.no_generic_json_parser import NoGenericJsonParser
64
65
  from sarj_python_lint.rules.no_generic_single_export_module import NoGenericSingleExportModule
65
66
  from sarj_python_lint.rules.no_hidden_constructor_fallback import (
66
67
  NoHiddenConstructorFallback,
@@ -73,6 +74,7 @@ from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
73
74
  from sarj_python_lint.rules.no_loguru_printf_format import NoLoguruPrintfFormat
74
75
  from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
75
76
  from sarj_python_lint.rules.no_mutating_container_transform import NoMutatingContainerTransform
77
+ from sarj_python_lint.rules.no_mutating_input_with_result import NoMutatingInputWithResult
76
78
  from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
77
79
  from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
78
80
  from sarj_python_lint.rules.no_offset_pagination import NoOffsetPagination
@@ -83,6 +85,7 @@ from sarj_python_lint.rules.no_psycopg_execution_outside_injected_owner import (
83
85
  from sarj_python_lint.rules.no_random_uuid_in_sql import NoRandomUuidInSql
84
86
  from sarj_python_lint.rules.no_raw_connection_in_tests import NoRawConnectionInTests
85
87
  from sarj_python_lint.rules.no_raw_source_text_test_oracle import NoRawSourceTextTestOracle
88
+ from sarj_python_lint.rules.no_redundant_attribute_unpacking import NoRedundantAttributeUnpacking
86
89
  from sarj_python_lint.rules.no_redundant_literal_description import NoRedundantLiteralDescription
87
90
  from sarj_python_lint.rules.no_redundant_module_alias_exports import NoRedundantModuleAliasExports
88
91
  from sarj_python_lint.rules.no_repeated_string_literal import NoRepeatedStringLiteral
@@ -95,6 +98,7 @@ from sarj_python_lint.rules.no_shallow_container_type_guard import NoShallowCont
95
98
  from sarj_python_lint.rules.no_string_concat_in_loop import NoStringConcatInLoop
96
99
  from sarj_python_lint.rules.no_swallowed_asyncio_cancellation import NoSwallowedAsyncioCancellation
97
100
  from sarj_python_lint.rules.no_tautological_expect import NoTautologicalExpect
101
+ from sarj_python_lint.rules.no_test_comments import NoTestComments
98
102
  from sarj_python_lint.rules.no_test_method_grafting import NoTestMethodGrafting
99
103
  from sarj_python_lint.rules.no_typed_doc_sections import NoTypedDocSections
100
104
  from sarj_python_lint.rules.no_unique_violation_message_match import (
@@ -149,6 +153,7 @@ from sarj_python_lint.rules.prefer_non_nullable_collection import (
149
153
  )
150
154
  from sarj_python_lint.rules.prefer_or_pattern import PreferOrPattern
151
155
  from sarj_python_lint.rules.prefer_pydantic_json_value import PreferPydanticJsonValue
156
+ from sarj_python_lint.rules.prefer_pytest_fixture_contract import PreferPytestFixtureContract
152
157
  from sarj_python_lint.rules.prefer_pytest_fixture_injection import PreferPytestFixtureInjection
153
158
  from sarj_python_lint.rules.prefer_regex_fullmatch import PreferRegexFullmatch
154
159
  from sarj_python_lint.rules.prefer_required_constructor_parameters import PreferRequiredConstructorParameters
@@ -175,6 +180,7 @@ from sarj_python_lint.rules.redundant_module_docstring import RedundantModuleDoc
175
180
  from sarj_python_lint.rules.repeated_kwarg_heavy_call_in_test import RepeatedKwargHeavyCallInTest
176
181
  from sarj_python_lint.rules.repeated_static_call_cases import RepeatedStaticCallCases
177
182
  from sarj_python_lint.rules.repeated_test_composition import RepeatedTestComposition
183
+ from sarj_python_lint.rules.require_constrained_api_integer import RequireConstrainedApiInteger
178
184
  from sarj_python_lint.rules.require_explicit_contract_implementation import RequireExplicitContractImplementation
179
185
  from sarj_python_lint.rules.require_explicit_psycopg_transaction import RequireExplicitPsycopgTransaction
180
186
  from sarj_python_lint.rules.require_explicit_service_contract import RequireExplicitServiceContract
@@ -437,6 +443,12 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
437
443
  PreferNativeStringCheck.id: PreferNativeStringCheck,
438
444
  PreferSelfDocumentingConstant.id: PreferSelfDocumentingConstant,
439
445
  NoDuplicateDunderAllEntry.id: NoDuplicateDunderAllEntry,
446
+ PreferPytestFixtureContract.id: PreferPytestFixtureContract,
447
+ NoTestComments.id: NoTestComments,
448
+ NoGenericJsonParser.id: NoGenericJsonParser,
449
+ RequireConstrainedApiInteger.id: RequireConstrainedApiInteger,
450
+ NoRedundantAttributeUnpacking.id: NoRedundantAttributeUnpacking,
451
+ NoMutatingInputWithResult.id: NoMutatingInputWithResult,
440
452
  NoDunderAll.id: NoDunderAll,
441
453
  NoDeleteStatement.id: NoDeleteStatement,
442
454
  NoBroadKeywordCapture.id: NoBroadKeywordCapture,
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from sarj_python_lint._file_context import PythonFileContext
23
+ from sarj_python_lint.rules._imports import ImportIndex
24
+
25
+
26
+ _PYDANTIC = frozenset({"pydantic", "pydantic.root_model", "pydantic.type_adapter", "pydantic.types"})
27
+ _TYPING = frozenset({"typing", "typing_extensions"})
28
+
29
+
30
+ @final
31
+ class NoGenericJsonParser(Rule):
32
+ id = "no-generic-json-parser"
33
+ code = "SARJ552"
34
+ documentation = RuleDocumentation(
35
+ default_level=Severity.WARNING,
36
+ summary="Parse consumed JSON fields with a bespoke declarative model instead of an open JSON parser.",
37
+ rationale="An open JSON domain validates syntax and value kinds without declaring required fields or their relationships; consumers must then reconstruct the contract manually.",
38
+ remediation="Declare only the fields the consumer needs in a named Pydantic model and use model_validate_json or model_validate. Allow unrelated fields when the wire contract permits them. Document an exact-code exception for genuinely schema-agnostic infrastructure.",
39
+ category=RuleCategory.CORRECTNESS,
40
+ autofix=AutofixPolicy.NONE,
41
+ limitations=(
42
+ "Reports import-proven TypeAdapter construction and RootModel bases containing JsonValue, Json, Any or object, including module-local explicit type aliases. Tests remain in scope; generated files are excluded.",
43
+ "Typed dictionaries of domain values, model fields storing arbitrary JSON, opaque imported aliases and custom parser classes are excluded. Recursive arbitrary unions without a canonical open type are not inferred.",
44
+ "Generic schema infrastructure may need a reasoned local suppression. No model or complete request schema is synthesized automatically.",
45
+ ),
46
+ examples=(
47
+ RuleExample(
48
+ example_id="generic-response-parser",
49
+ title="An open JSON mapping does not declare the response fields",
50
+ outcome=ExampleOutcome.MATCH,
51
+ files=(
52
+ ExampleFile.python(
53
+ "tests/test_api.py",
54
+ "from pydantic import JsonValue, RootModel\nclass Response(RootModel[dict[str, JsonValue]]):\n pass\n",
55
+ ),
56
+ ),
57
+ focus_path=PurePosixPath("tests/test_api.py"),
58
+ expected_count=1,
59
+ public=True,
60
+ ),
61
+ RuleExample(
62
+ example_id="partial-declarative-response",
63
+ title="Declare just the field this consumer needs",
64
+ outcome=ExampleOutcome.NO_MATCH,
65
+ files=(
66
+ ExampleFile.python(
67
+ "tests/test_api.py",
68
+ "from pydantic import BaseModel\nclass Response(BaseModel):\n version: int\n",
69
+ ),
70
+ ),
71
+ focus_path=PurePosixPath("tests/test_api.py"),
72
+ expected_count=0,
73
+ public=True,
74
+ ),
75
+ ),
76
+ )
77
+ description = documentation.summary
78
+
79
+ @override
80
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
81
+ if context.generated or context.tree is None:
82
+ return []
83
+ imports = context.imports
84
+ aliases = {
85
+ statement.name.id: statement.value
86
+ for statement in context.tree.body
87
+ if isinstance(statement, ast.TypeAlias)
88
+ }
89
+ candidates = _open_parser_nodes(context, imports, aliases)
90
+ return [
91
+ Diagnostic(
92
+ context.path,
93
+ node.lineno,
94
+ node.col_offset + 1,
95
+ self.code,
96
+ "Replace this open JSON parser with a named model declaring the consumed fields.",
97
+ severity=Severity.WARNING,
98
+ )
99
+ for node in candidates
100
+ if not is_suppressed(context.source_lines, node.lineno, self.code)
101
+ ]
102
+
103
+
104
+ def _open_parser_nodes(
105
+ context: PythonFileContext, imports: ImportIndex, aliases: dict[str, ast.expr]
106
+ ) -> list[ast.expr]:
107
+ candidates: list[ast.expr] = []
108
+ for call in context.nodes(ast.Call):
109
+ if not imports.resolves(call.func, sources=_PYDANTIC, symbol="TypeAdapter"):
110
+ continue
111
+ schema = call.args[0] if call.args else next((kw.value for kw in call.keywords if kw.arg == "type"), None)
112
+ if schema is not None and _open_schema(schema, imports, aliases):
113
+ candidates.append(call)
114
+ for owner in context.nodes(ast.ClassDef):
115
+ candidates.extend(_open_root_bases(owner, imports, aliases))
116
+ return sorted(candidates, key=lambda node: (node.lineno, node.col_offset))
117
+
118
+
119
+ def _open_root_bases(owner: ast.ClassDef, imports: ImportIndex, aliases: dict[str, ast.expr]) -> list[ast.Subscript]:
120
+ return [
121
+ base
122
+ for base in owner.bases
123
+ if isinstance(base, ast.Subscript)
124
+ and imports.resolves(base.value, sources=_PYDANTIC, symbol="RootModel")
125
+ and _open_schema(base.slice, imports, aliases)
126
+ ]
127
+
128
+
129
+ def _open_schema(
130
+ node: ast.expr, imports: ImportIndex, aliases: dict[str, ast.expr], seen: frozenset[str] = frozenset()
131
+ ) -> bool:
132
+ for item in ast.walk(node):
133
+ if not isinstance(item, ast.expr):
134
+ continue
135
+ if any(imports.resolves(item, sources=_PYDANTIC, symbol=name) for name in ("JsonValue", "Json")):
136
+ return True
137
+ if imports.resolves(item, sources=_TYPING, symbol="Any"):
138
+ return True
139
+ if isinstance(item, ast.Name):
140
+ if item.id == "object" and imports.builtin_is_unshadowed("object"):
141
+ return True
142
+ if (
143
+ item.id in aliases
144
+ and item.id not in seen
145
+ and _open_schema(aliases[item.id], imports, aliases, seen | {item.id})
146
+ ):
147
+ return True
148
+ return False
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ ProjectRule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+ from sarj_python_lint.rules._test_composition import direct_nodes
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+
25
+
26
+ _MAX_ALIAS_DEPTH = 8
27
+ _BAD = "from attrs import define\n@define\nclass Request:\n expected_version: int\ndef send_update(body: Request) -> Response:\n body.expected_version = 2\n return send(body)\n"
28
+ _GOOD = "from attrs import define, evolve\n@define\nclass Request:\n expected_version: int\ndef send_update(body: Request) -> Response:\n updated = evolve(body, expected_version=2)\n return send(updated)\n"
29
+
30
+
31
+ @final
32
+ class NoMutatingInputWithResult(ProjectRule):
33
+ id = "no-mutating-input-with-result"
34
+ code = "SARJ555"
35
+ documentation = RuleDocumentation(
36
+ default_level=Severity.WARNING,
37
+ summary="Separate input attribute mutation from functions that return a result.",
38
+ rationale="Returning an operation result can hide writes to a request object retained by the caller. Reusing that input then observes state changed by an earlier call.",
39
+ remediation="Build and return a replacement input before sending it, or expose a deliberate in-place command returning None. Preserve response values and input contracts; do not discard the result to silence the rule.",
40
+ category=RuleCategory.CORRECTNESS,
41
+ autofix=AutofixPolicy.NONE,
42
+ limitations=(
43
+ "Checks direct attribute assignments, augmented assignments and deletions through a parameter or an unambiguously assigned local alias in functions with an explicit non-None annotation and at least one value return. No class-name or model-library inference is needed.",
44
+ "Pydantic writes and before-validator writes keep their existing SARJ481/SARJ482 owners. Builtin-container item mutation remains with SARJ520; this rule does not duplicate subscript diagnostics.",
45
+ "None-returning commands, receiver state named self/cls, fresh copies, nested attributes, opaque mutator calls, generators, missing return annotations and unresolved or more than eight alias links are excluded.",
46
+ "Intentional mutation-and-result APIs may retain a reasoned exact suppression. Warnings require review; no copying, return-type change or lifecycle rewrite is automatic.",
47
+ ),
48
+ examples=(
49
+ RuleExample(
50
+ example_id="attrs-request-mutated-before-response",
51
+ title="Sending a generated request changes the caller's request",
52
+ outcome=ExampleOutcome.MATCH,
53
+ files=(ExampleFile.python("api.py", _BAD),),
54
+ focus_path=PurePosixPath("api.py"),
55
+ expected_count=1,
56
+ public=True,
57
+ ),
58
+ RuleExample(
59
+ example_id="attrs-request-replaced-before-response",
60
+ title="Send a replacement and keep the input unchanged",
61
+ outcome=ExampleOutcome.NO_MATCH,
62
+ files=(ExampleFile.python("api.py", _GOOD),),
63
+ focus_path=PurePosixPath("api.py"),
64
+ expected_count=0,
65
+ public=True,
66
+ ),
67
+ ),
68
+ )
69
+ description = documentation.summary
70
+
71
+ @override
72
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
73
+ if context.tree is None or context.generated:
74
+ return []
75
+ owned = {mutation.node for mutation in context.copy_on_write.mutations}
76
+ findings: list[Diagnostic] = []
77
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
78
+ if not _returns_result(function, context):
79
+ continue
80
+ for target in _attribute_writes(function):
81
+ if target in owned or is_suppressed(context.source_lines, target.lineno, self.code):
82
+ continue
83
+ name = _input_owner(target.value, function, context)
84
+ if name is not None:
85
+ findings.append(
86
+ Diagnostic(
87
+ context.path,
88
+ target.lineno,
89
+ target.col_offset + 1,
90
+ self.code,
91
+ f"This result-returning function also mutates input `{name}`; return a replacement or make the in-place command's None contract explicit.",
92
+ severity=Severity.WARNING,
93
+ )
94
+ )
95
+ return findings
96
+
97
+
98
+ def _returns_result(function: ast.FunctionDef | ast.AsyncFunctionDef, context: PythonFileContext) -> bool:
99
+ annotation = context.annotation_semantics.resolve_alias(function.returns)
100
+ if annotation is None or (isinstance(annotation, ast.Constant) and annotation.value is None):
101
+ return False
102
+ nodes = tuple(direct_nodes(function))
103
+ if any(isinstance(node, ast.Yield | ast.YieldFrom) for node in nodes):
104
+ return False
105
+ return any(
106
+ isinstance(node, ast.Return)
107
+ and node.value is not None
108
+ and not (isinstance(node.value, ast.Constant) and node.value.value is None)
109
+ for node in nodes
110
+ )
111
+
112
+
113
+ def _attribute_writes(function: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[ast.Attribute, ...]:
114
+ return tuple(
115
+ node
116
+ for node in direct_nodes(function)
117
+ if isinstance(node, ast.Attribute)
118
+ and isinstance(node.ctx, ast.Store | ast.Del)
119
+ and isinstance(node.value, ast.Name)
120
+ )
121
+
122
+
123
+ def _input_owner(
124
+ expression: ast.expr,
125
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
126
+ context: PythonFileContext,
127
+ depth: int = 0,
128
+ ) -> str | None:
129
+ if not isinstance(expression, ast.Name) or depth >= _MAX_ALIAS_DEPTH:
130
+ return None
131
+ binding = context.static_resource_provenance.binding(expression.id, expression)
132
+ if isinstance(binding, ast.arg):
133
+ arguments = (*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs)
134
+ return binding.arg if binding in arguments and binding.arg not in {"self", "cls"} else None
135
+ if isinstance(binding, ast.Name):
136
+ value = context.static_resource_provenance.assignment_value(binding, expression)
137
+ if value is not None:
138
+ return _input_owner(value, function, context, depth + 1)
139
+ return None
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from pathlib import PurePosixPath
6
+ from typing import TYPE_CHECKING, final, override
7
+
8
+ from sarj_python_lint.rule_base import (
9
+ AutofixPolicy,
10
+ Diagnostic,
11
+ ExampleFile,
12
+ ExampleOutcome,
13
+ Rule,
14
+ RuleCategory,
15
+ RuleDocumentation,
16
+ RuleExample,
17
+ Severity,
18
+ is_suppressed,
19
+ )
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+
25
+
26
+ _MIN_ATTRIBUTES = 2
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class _AttributeAlias:
31
+ target: str
32
+ receiver: str
33
+
34
+
35
+ @final
36
+ class NoRedundantAttributeUnpacking(Rule):
37
+ id = "no-redundant-attribute-unpacking"
38
+ code = "SARJ554"
39
+ documentation = RuleDocumentation(
40
+ default_level=Severity.WARNING,
41
+ summary="Use an aggregate's named attributes instead of repeating their names in tuple aliases.",
42
+ rationale="A tuple assignment that repeats every attribute name adds parallel bindings without describing a transformation or a new concept.",
43
+ remediation="Read the aggregate's named attributes directly. Retain intentional value snapshots with an explained local suppression.",
44
+ category=RuleCategory.MAINTAINABILITY,
45
+ autofix=AutofixPolicy.NONE,
46
+ limitations=(
47
+ "Reports unconditional tuple/list assignments directly in function bodies only when every element aliases an identically named attribute of the same simple receiver and no target rebinds that receiver.",
48
+ "Conditional/loop/context-manager aliases can normalize branch results or preserve resource scopes and are excluded. Renamed attributes, mixed receivers, nested unpacking, calls, module/class bindings, generated source and malformed source are excluded.",
49
+ "This is an explicitly requested warning-level style preference. A retained snapshot can be meaningful when the aggregate changes; no autofix or claim of behavioral equivalence is made.",
50
+ ),
51
+ examples=(
52
+ RuleExample(
53
+ example_id="repeated-attribute-names",
54
+ title="Keep the named result aggregate",
55
+ outcome=ExampleOutcome.MATCH,
56
+ files=(
57
+ ExampleFile.python(
58
+ "tests/test_save.py",
59
+ "def test_save(seeded):\n scenario, current = seeded.scenario, seeded.current\n assert scenario.id == current.id\n",
60
+ ),
61
+ ),
62
+ focus_path=PurePosixPath("tests/test_save.py"),
63
+ expected_count=1,
64
+ public=True,
65
+ ),
66
+ RuleExample(
67
+ example_id="direct-attribute-use",
68
+ title="Read names on their owner",
69
+ outcome=ExampleOutcome.NO_MATCH,
70
+ files=(
71
+ ExampleFile.python(
72
+ "tests/test_save.py",
73
+ "def test_save(seeded):\n assert seeded.scenario.id == seeded.current.id\n",
74
+ ),
75
+ ),
76
+ focus_path=PurePosixPath("tests/test_save.py"),
77
+ expected_count=0,
78
+ public=True,
79
+ ),
80
+ ),
81
+ )
82
+ description = documentation.summary
83
+
84
+ @override
85
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
86
+ if context.tree is None or context.generated:
87
+ return []
88
+ findings: list[Diagnostic] = []
89
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
90
+ for node in function.body:
91
+ if (
92
+ not isinstance(node, ast.Assign)
93
+ or not _redundant_unpacking(node)
94
+ or is_suppressed(context.source_lines, node.lineno, self.code)
95
+ ):
96
+ continue
97
+ findings.append(
98
+ Diagnostic(
99
+ context.path,
100
+ node.lineno,
101
+ node.col_offset + 1,
102
+ self.code,
103
+ "Use these named attributes on their aggregate directly; retain intentional snapshots with an explained exception.",
104
+ severity=Severity.WARNING,
105
+ )
106
+ )
107
+ return list({(finding.line, finding.col): finding for finding in findings}.values())
108
+
109
+
110
+ def _redundant_unpacking(node: ast.Assign) -> bool:
111
+ if len(node.targets) != 1:
112
+ return False
113
+ target = node.targets[0]
114
+ if not isinstance(target, ast.Tuple | ast.List) or not isinstance(node.value, ast.Tuple | ast.List):
115
+ return False
116
+ if len(target.elts) < _MIN_ATTRIBUTES or len(target.elts) != len(node.value.elts):
117
+ return False
118
+ receivers: set[str] = set()
119
+ targets: set[str] = set()
120
+ for left, right in zip(target.elts, node.value.elts, strict=True):
121
+ pair = _attribute_alias(left, right)
122
+ if pair is None:
123
+ return False
124
+ targets.add(pair.target)
125
+ receivers.add(pair.receiver)
126
+ return len(receivers) == 1 and receivers.isdisjoint(targets)
127
+
128
+
129
+ def _attribute_alias(left: ast.expr, right: ast.expr) -> _AttributeAlias | None:
130
+ if not isinstance(left, ast.Name) or not isinstance(right, ast.Attribute) or not isinstance(right.value, ast.Name):
131
+ return None
132
+ return _AttributeAlias(left.id, right.value.id) if left.id == right.attr else None
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import PurePosixPath
4
+ import re
5
+ from typing import TYPE_CHECKING, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+ from sarj_python_lint.rules._comments import PositionedComment, comment_runs, has_legal_notice
20
+ from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
21
+ from sarj_python_lint.rules._pytest import collected_tests
22
+ from sarj_python_lint.rules._suppression_comments import scan_comments_or_none
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Sequence
27
+
28
+ from sarj_python_lint._file_context import PythonFileContext
29
+ from sarj_python_lint.rules._suppression_comments import Comment
30
+
31
+
32
+ _DIRECTIVE = re.compile(
33
+ r"^(?:!|.*?coding[:=]|(?:sarj-noqa|noqa|ruff|pyright|basedpyright|type|fmt|isort|pragma|"
34
+ r"mypy|pylint|nosec|doctest|pytest|uv|region|endregion)\s*[:\[]|(?:fmt|isort):|%%)",
35
+ re.IGNORECASE,
36
+ )
37
+
38
+
39
+ @final
40
+ class NoTestComments(Rule):
41
+ id = "no-test-comments"
42
+ code = "SARJ551"
43
+ documentation = RuleDocumentation(
44
+ default_level=Severity.WARNING,
45
+ summary="Express test behavior in names and assertions instead of ordinary comments.",
46
+ rationale="Test prose can drift from the setup and assertion it describes; executable contracts keep the explanation checked.",
47
+ remediation="Remove the comment, clarify the test or helper name, or express the invariant with an assertion. Preserve legal notices and required tool directives.",
48
+ category=RuleCategory.TESTING,
49
+ autofix=AutofixPolicy.NONE,
50
+ limitations=(
51
+ "Checks real comment tokens in recognized test and test-support paths, including inline comments; string contents and docstrings are outside this policy.",
52
+ "Ambiguous test-like filenames require collected test declarations; test and test-support directories and conftest own their source independently of declarations.",
53
+ "Generated files, malformed source, legal notices, shebangs, encoding declarations and tool directives are excluded. Existing comment rules retain their own diagnostic ownership.",
54
+ "This is an explicitly requested style policy, released as a warning. No automatic deletion of potentially useful rationale is offered.",
55
+ ),
56
+ examples=(
57
+ RuleExample(
58
+ example_id="comment-narrates-test",
59
+ title="Name the tested behavior",
60
+ outcome=ExampleOutcome.MATCH,
61
+ files=(
62
+ ExampleFile.python(
63
+ "tests/test_save.py",
64
+ "def test_save():\n # A stale save is rejected.\n assert save_is_rejected()\n",
65
+ ),
66
+ ),
67
+ focus_path=PurePosixPath("tests/test_save.py"),
68
+ expected_count=1,
69
+ public=True,
70
+ ),
71
+ RuleExample(
72
+ example_id="behavior-in-test-name",
73
+ title="The assertion documents the behavior",
74
+ outcome=ExampleOutcome.NO_MATCH,
75
+ files=(
76
+ ExampleFile.python(
77
+ "tests/test_save.py", "def test_stale_save_is_rejected():\n assert save_is_rejected()\n"
78
+ ),
79
+ ),
80
+ focus_path=PurePosixPath("tests/test_save.py"),
81
+ expected_count=0,
82
+ public=True,
83
+ ),
84
+ ),
85
+ )
86
+ description = documentation.summary
87
+
88
+ @override
89
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
90
+ if context.generated or context.tree is None or not _test_comment_source(context):
91
+ return []
92
+ comments = scan_comments_or_none(context.source) or ()
93
+ legal_lines = _legal_comment_lines(comments)
94
+ return [
95
+ Diagnostic(
96
+ path=context.path,
97
+ line=comment.line,
98
+ col=comment.col,
99
+ code=self.code,
100
+ message="Move this test comment's meaning into a test name, helper or assertion.",
101
+ severity=Severity.WARNING,
102
+ )
103
+ for comment in comments
104
+ if comment.body.strip()
105
+ and comment.line not in legal_lines
106
+ and not _DIRECTIVE.search(comment.body)
107
+ and not has_legal_notice(comment.body)
108
+ and not is_suppressed(context.source_lines, comment.line, self.code)
109
+ ]
110
+
111
+
112
+ def _test_comment_source(context: PythonFileContext) -> bool:
113
+ directory = context.path.with_name("__init__.py")
114
+ if context.path.name == "conftest.py" or is_test_path(directory) or is_test_support_path(directory):
115
+ return True
116
+ return context.tree is not None and is_test_path(context.path) and any(collected_tests(context.tree.body))
117
+
118
+
119
+ def _legal_comment_lines(comments: Sequence[Comment]) -> set[int]:
120
+ standalone = [
121
+ PositionedComment(comment.line, comment.col, comment.body) for comment in comments if comment.standalone
122
+ ]
123
+ protected: set[int] = set()
124
+ for run in comment_runs(standalone):
125
+ if any(has_legal_notice(body) for _, _, body in run):
126
+ protected.update(line for line, _, _ in run)
127
+ return protected