sarj-python-lint 1.22.1__tar.gz → 1.25.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 (182) hide show
  1. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +27 -14
  4. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +103 -14
  5. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +302 -111
  6. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/.gitignore +0 -0
  7. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/LICENSE +0 -0
  8. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/README.md +0 -0
  9. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/_version.py +0 -0
  18. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/benchmark.py +0 -0
  19. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/json_boundary.py +0 -0
  20. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/py.typed +0 -0
  21. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/ratchet.py +0 -0
  22. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rule_base.py +0 -0
  23. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  24. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  25. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  26. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  27. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  28. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  29. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  30. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  31. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  32. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  33. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  34. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  35. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  36. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  37. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  38. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  39. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  40. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  41. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  42. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  43. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  44. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  45. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  46. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  47. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  48. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  49. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  50. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  51. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  52. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  53. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  54. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  55. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  56. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  57. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  58. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  59. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  60. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  61. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  62. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  63. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  64. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  65. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  66. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  67. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  68. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  69. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  70. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  71. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  72. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  73. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  74. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  75. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  76. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  77. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  78. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  79. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  80. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  81. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  82. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  83. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  84. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  85. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  86. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  87. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  88. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  89. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  90. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  91. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  92. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  93. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  94. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  95. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  96. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  97. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  98. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  99. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  100. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  101. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  102. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  103. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  104. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  105. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  106. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  107. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  108. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  109. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  110. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  111. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  112. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  113. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  114. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  115. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  116. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  117. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  118. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  119. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  120. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  121. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  122. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  123. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  124. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  125. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  126. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  127. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  128. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  129. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  130. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  131. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  132. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  133. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  134. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  135. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  136. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  137. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  138. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  139. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  140. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  141. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  142. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  143. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  144. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  145. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  146. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  147. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  148. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  149. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  150. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  151. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  152. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  153. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  154. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  155. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  156. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  157. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  158. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  159. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  160. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  161. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  162. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  163. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  164. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  165. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  166. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  167. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  168. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  169. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  170. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  171. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  172. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  173. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  174. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  175. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  176. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  177. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  178. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  179. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  180. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  181. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  182. {sarj_python_lint-1.22.1 → sarj_python_lint-1.25.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.22.1
3
+ Version: 1.25.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.22.1"
3
+ version = "1.25.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" }]
@@ -133,18 +133,8 @@ class NoPsycopgExecutionOutsideInjectedOwner(Rule):
133
133
  or _is_operational_path(path)
134
134
  ):
135
135
  return []
136
- tree = context.tree
137
- if not isinstance(tree, ast.Module):
138
- return []
139
- types = _type_index(tree)
140
- ownership = {
141
- node: _class_ownership(node, types) for node in context.nodes(ast.AST) if isinstance(node, ast.ClassDef)
142
- }
143
136
  source_lines = context.source_lines
144
- calls: list[ast.Call] = []
145
- for scope in _function_scopes(tree):
146
- owned = ownership[scope.owner] if scope.owner is not None else _ClassOwnership(frozenset(), frozenset())
147
- calls.extend(_FunctionAnalyzer(types, owned).analyze(scope.function))
137
+ calls = psycopg_execution_calls(context, include_injected=False, include_probes=False)
148
138
  unique = {(call.lineno, call.col_offset): call for call in calls}
149
139
  return [
150
140
  Diagnostic(
@@ -163,6 +153,25 @@ class NoPsycopgExecutionOutsideInjectedOwner(Rule):
163
153
  ]
164
154
 
165
155
 
156
+ def psycopg_execution_calls(
157
+ context: PythonFileContext, *, include_injected: bool, include_probes: bool
158
+ ) -> list[ast.Call]:
159
+ tree = context.tree
160
+ if tree is None:
161
+ return []
162
+ types = _type_index(tree)
163
+ ownership = {node: _class_ownership(node, types) for node in context.nodes(ast.ClassDef)}
164
+ calls: list[ast.Call] = []
165
+ for scope in _function_scopes(tree):
166
+ owned = ownership[scope.owner] if scope.owner is not None else _ClassOwnership(frozenset(), frozenset())
167
+ calls.extend(
168
+ _FunctionAnalyzer(types, owned, include_injected=include_injected, include_probes=include_probes).analyze(
169
+ scope.function
170
+ )
171
+ )
172
+ return calls
173
+
174
+
166
175
  def _type_index(tree: ast.Module) -> _TypeIndex:
167
176
  counts = _module_binding_counts(tree)
168
177
  aliases = _module_type_aliases(tree, counts)
@@ -416,9 +425,13 @@ def _function_scopes(tree: ast.Module) -> list[_FunctionScope]:
416
425
 
417
426
  @final
418
427
  class _FunctionAnalyzer:
419
- def __init__(self, types: _TypeIndex, ownership: _ClassOwnership) -> None:
428
+ def __init__(
429
+ self, types: _TypeIndex, ownership: _ClassOwnership, *, include_injected: bool, include_probes: bool
430
+ ) -> None:
420
431
  self._types = types
421
432
  self._ownership = ownership
433
+ self._allowed_origins = frozenset(_Origin) if include_injected else frozenset({_Origin.EXTERNAL})
434
+ self._include_probes = include_probes
422
435
  self._findings: list[ast.Call] = []
423
436
  self._shadowed: set[str] = set()
424
437
 
@@ -578,8 +591,8 @@ class _FunctionAnalyzer:
578
591
  if (
579
592
  isinstance(node.func, ast.Attribute)
580
593
  and node.func.attr in _EXECUTE_METHODS
581
- and analyzer._expression_origin(node.func.value, state) is _Origin.EXTERNAL
582
- and not _is_connection_probe(node)
594
+ and analyzer._expression_origin(node.func.value, state) in analyzer._allowed_origins
595
+ and (analyzer._include_probes or not _is_connection_probe(node))
583
596
  ):
584
597
  analyzer._findings.append(node)
585
598
  self.generic_visit(node)
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  from pathlib import PurePosixPath
5
+ import re
5
6
  from typing import TYPE_CHECKING, ClassVar, final, override
6
7
 
7
8
  from sarj_python_lint.rule_base import (
@@ -14,9 +15,12 @@ from sarj_python_lint.rule_base import (
14
15
  RuleDocumentation,
15
16
  RuleExample,
16
17
  Severity,
18
+ is_suppressed,
17
19
  )
18
20
  from sarj_python_lint.rules._ast_index import walk as walk_ast
19
21
  from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
22
+ from sarj_python_lint.rules._sql import sql_string_value, strip_sql_noise
23
+ from sarj_python_lint.rules.no_psycopg_execution_outside_injected_owner import psycopg_execution_calls
20
24
 
21
25
 
22
26
  if TYPE_CHECKING:
@@ -26,6 +30,17 @@ if TYPE_CHECKING:
26
30
 
27
31
 
28
32
  _POOL_TYPES = frozenset({"AsyncConnectionPool", "ConnectionPool"})
33
+ _SQL_METHODS = frozenset(
34
+ {"execute", "executemany", "executescript", "exec_driver_sql", "fetch", "fetchrow", "fetchval", "query", "command"}
35
+ )
36
+ _QUERY_KEYWORDS = frozenset({"query", "sql", "statement", "command", "operation"})
37
+ _SQL_START = re.compile(
38
+ r"\A\s*(?:SELECT\b|WITH\s+\S+[\s\S]*?\bAS\s*\(|INSERT\s+INTO\b|"
39
+ r"UPDATE\s+\S+[\s\S]*?\bSET\b|DELETE\s+FROM\b|TRUNCATE\b|"
40
+ r"(?:CREATE|ALTER|DROP)\s+(?:TABLE|INDEX|SCHEMA|VIEW|FUNCTION|TRIGGER)\b|"
41
+ r"LOCK\s+TABLE\b|SET\s+(?:LOCAL|TRANSACTION)\b)",
42
+ re.IGNORECASE,
43
+ )
29
44
 
30
45
 
31
46
  @final
@@ -34,34 +49,35 @@ class NoRawConnectionInTests(Rule):
34
49
  code = "SARJ429"
35
50
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
36
51
  default_level=Severity.ERROR,
37
- summary="Do not acquire raw database connections in tests.",
52
+ summary="Do not execute raw SQL or acquire raw database connections in tests.",
38
53
  rationale=(
39
54
  "Tests that reach through a pool couple assertions and setup to persistence internals, bypass the "
40
55
  "application boundary, and duplicate transaction ownership."
41
56
  ),
42
57
  remediation=(
43
- "Exercise the owning store/service API or expose a narrow test-support fixture. Use an exact SARJ429 "
44
- "suppression for a test whose purpose is explicitly connection or transaction behavior."
58
+ "Exercise the owning store/service API. Do not move SQL into a test-only probe to hide it. Use an exact "
59
+ "SARJ429 suppression for deliberate database bootstrap, migration or transaction fault testing."
45
60
  ),
46
61
  category=RuleCategory.TESTING,
47
62
  autofix=AutofixPolicy.NONE,
63
+ aliases=("no-raw-sql-in-tests",),
48
64
  limitations=(
49
- "Only collected test modules outside conftest.py, conventional shared test-support modules, and migration-test trees are inspected.",
50
- "A receiver is reported only when a parameter, annotated local, or constructor call proves it is a psycopg ConnectionPool or AsyncConnectionPool.",
51
- "Pytest fixtures may use a connection internally for setup and cleanup, but fixtures that return or yield the connection remain reportable.",
65
+ "Raw SQL execution is checked in tests, conftest.py, shared test-support helpers and migration tests; generated code is excluded. Standalone SQL examples and store/service calls are not execution.",
66
+ "Import-proven Psycopg connection/pool flows, including constructor-injected members, aliases and dynamic query arguments, are followed. SQL-shaped literals in execute/query/fetch-like calls are also checked across drivers, including concatenation, f-strings and import-proven SQL/text constructors.",
67
+ "Opaque non-Psycopg builders and interprocedural receiver flows are not inferred. Raw connection acquisition keeps its existing fixture/support exclusions when no SQL execution is found in that scope.",
52
68
  ),
53
69
  examples=(
54
70
  RuleExample(
55
- example_id="test-acquires-pool-connection",
56
- title="A test helper reaches directly through its pool",
71
+ example_id="test-probe-executes-sql",
72
+ title="An injected test probe still bypasses the owning store",
57
73
  outcome=ExampleOutcome.MATCH,
58
74
  files=(
59
75
  ExampleFile.python(
60
- "tests/test_orders.py",
61
- "async def load_rows(pool: AsyncConnectionPool):\n async with pool.connection() as conn:\n return await conn.execute('SELECT 1')\n",
76
+ "tests/fixtures/probe.py",
77
+ "from psycopg_pool import AsyncConnectionPool\n\nclass Probe:\n def __init__(self, pool: AsyncConnectionPool):\n self._pool = pool\n\n async def seed(self):\n async with self._pool.connection() as conn:\n await conn.execute('INSERT INTO jobs (id) VALUES (%s)', (job_id,))\n",
62
78
  ),
63
79
  ),
64
- focus_path=PurePosixPath("tests/test_orders.py"),
80
+ focus_path=PurePosixPath("tests/fixtures/probe.py"),
65
81
  expected_count=1,
66
82
  public=True,
67
83
  ),
@@ -86,6 +102,20 @@ class NoRawConnectionInTests(Rule):
86
102
  @override
87
103
  def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
88
104
  path = context.path
105
+ if context.generated or context.tree is None:
106
+ return []
107
+ sql_calls = _test_sql_calls(context)
108
+ diagnostics = [
109
+ Diagnostic(
110
+ path=path,
111
+ line=node.lineno,
112
+ col=node.col_offset + 1,
113
+ code=self.code,
114
+ message="test code executes raw SQL; use the owning store/service API or an exact SARJ429 exception for deliberate database-level testing",
115
+ )
116
+ for node in sql_calls
117
+ if not is_suppressed(context.source_lines, node.lineno, self.code)
118
+ ]
89
119
  excluded_path = any(
90
120
  (
91
121
  not is_test_path(path),
@@ -95,17 +125,19 @@ class NoRawConnectionInTests(Rule):
95
125
  _is_migration_test(path),
96
126
  )
97
127
  )
98
- if excluded_path or context.generated:
99
- return []
128
+ if excluded_path:
129
+ return sorted(diagnostics, key=lambda item: (item.line, item.col))
100
130
  tree = context.tree
101
131
  if tree is None:
102
132
  return []
103
- diagnostics: list[Diagnostic] = []
133
+ sql_scopes = {_containing_scope(context, node) for node in sql_calls}
104
134
  scopes: list[ast.Module | ast.FunctionDef | ast.AsyncFunctionDef] = [
105
135
  tree,
106
136
  *(node for node in context.nodes(ast.AST) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))),
107
137
  ]
108
138
  for scope in scopes:
139
+ if scope in sql_scopes:
140
+ continue
109
141
  pool_names = _proven_pool_names(scope)
110
142
  diagnostics.extend(
111
143
  Diagnostic(
@@ -129,6 +161,63 @@ class NoRawConnectionInTests(Rule):
129
161
  return sorted(diagnostics, key=lambda item: (item.line, item.col))
130
162
 
131
163
 
164
+ def _test_sql_calls(context: PythonFileContext) -> list[ast.Call]:
165
+ path = context.path
166
+ if not (is_test_path(path) or is_test_support_path(path) or "test_support" in path.parts):
167
+ return []
168
+ candidates = [
169
+ node
170
+ for node in context.nodes(ast.Call)
171
+ if isinstance(node.func, ast.Attribute) and node.func.attr in _SQL_METHODS
172
+ ]
173
+ if not candidates:
174
+ return []
175
+ calls: list[ast.Call] = []
176
+ for node in candidates:
177
+ value = _execution_sql(node, context)
178
+ if value is not None and _SQL_START.search(strip_sql_noise(value)):
179
+ calls.append(node)
180
+ if any(
181
+ isinstance(node.func, ast.Attribute) and node.func.attr in {"execute", "executemany"} and node not in calls
182
+ for node in candidates
183
+ ):
184
+ calls.extend(psycopg_execution_calls(context, include_injected=True, include_probes=True))
185
+ unique = {(call.lineno, call.col_offset): call for call in calls}
186
+ return sorted(unique.values(), key=lambda item: (item.lineno, item.col_offset))
187
+
188
+
189
+ def _execution_sql(call: ast.Call, context: PythonFileContext) -> str | None:
190
+ if call.args:
191
+ return _literal_sql(call.args[0], context)
192
+ for keyword in call.keywords:
193
+ if keyword.arg in _QUERY_KEYWORDS:
194
+ return _literal_sql(keyword.value, context)
195
+ return None
196
+
197
+
198
+ def _literal_sql(node: ast.expr | None, context: PythonFileContext) -> str | None:
199
+ if node is None:
200
+ return None
201
+ if isinstance(node, ast.Call):
202
+ if isinstance(node.func, ast.Attribute) and node.func.attr == "format":
203
+ return _literal_sql(node.func.value, context)
204
+ if node.args and (
205
+ context.imports.resolves(node.func, sources=frozenset({"psycopg.sql"}), symbol="SQL")
206
+ or context.imports.resolves(node.func, sources=frozenset({"sqlalchemy"}), symbol="text")
207
+ ):
208
+ return sql_string_value(node.args[0])
209
+ return sql_string_value(node)
210
+
211
+
212
+ def _containing_scope(context: PythonFileContext, node: ast.AST) -> ast.AST:
213
+ current = node
214
+ while (parent := context.parents.get(current)) is not None:
215
+ if isinstance(parent, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef)):
216
+ return parent
217
+ current = parent
218
+ return current
219
+
220
+
132
221
  def _is_non_collected_test_support(path: Path) -> bool:
133
222
  return "tests" in path.parts and not (path.name.startswith("test_") or path.name.endswith("_test.py"))
134
223