sarj-python-lint 1.19.0__tar.gz → 1.20.1__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 (178) hide show
  1. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +10 -5
  5. sarj_python_lint-1.20.1/src/sarj_python_lint/rules/prefer_native_string_check.py +169 -0
  6. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/.gitignore +0 -0
  7. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/LICENSE +0 -0
  8. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/README.md +0 -0
  9. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/_version.py +0 -0
  18. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/benchmark.py +0 -0
  19. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/json_boundary.py +0 -0
  20. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/py.typed +0 -0
  21. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/ratchet.py +0 -0
  22. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rule_base.py +0 -0
  23. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  24. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  25. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  26. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  27. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  28. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  29. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  30. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  31. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  32. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  33. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  34. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  35. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  36. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  37. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  38. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  39. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  40. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  41. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  42. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  43. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  44. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  45. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  46. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  47. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  48. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  49. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  50. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  51. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  52. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  53. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  54. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  55. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  56. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  57. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  58. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  59. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  60. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  61. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  62. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  63. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  64. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  65. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  66. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  67. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  68. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  69. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  70. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  71. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  72. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  73. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  74. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  75. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  76. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  77. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  78. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  79. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  80. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  81. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  82. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  83. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  84. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  85. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  86. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  87. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  88. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  89. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  90. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  91. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  92. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  93. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  94. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  95. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  96. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  97. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  98. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  99. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  100. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  101. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  102. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  103. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  104. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  105. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  106. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  107. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  108. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  109. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  110. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  111. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  112. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  113. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  114. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  115. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  116. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  117. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  118. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  119. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  120. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  121. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  122. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  123. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  124. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  125. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  126. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  127. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  128. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  129. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  130. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  131. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  132. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  133. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  134. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  135. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  136. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  137. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  138. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  139. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  140. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  141. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  142. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  143. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  144. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  145. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  146. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  147. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  148. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  149. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  150. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  151. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  152. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  153. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  154. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  155. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  156. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  157. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  158. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  159. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  160. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  161. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  162. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  163. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  164. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  165. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  166. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  167. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  168. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  169. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  170. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  171. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  172. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  173. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  174. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  175. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  176. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  177. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  178. {sarj_python_lint-1.19.0 → sarj_python_lint-1.20.1}/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.19.0
3
+ Version: 1.20.1
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.19.0"
3
+ version = "1.20.1"
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" }]
@@ -129,6 +129,7 @@ from sarj_python_lint.rules.prefer_monotonic_for_elapsed_time import PreferMonot
129
129
  from sarj_python_lint.rules.prefer_namedtuple_over_tuple_return import (
130
130
  PreferNamedtupleOverTupleReturn,
131
131
  )
132
+ from sarj_python_lint.rules.prefer_native_string_check import PreferNativeStringCheck
132
133
  from sarj_python_lint.rules.prefer_nominal_id_types import PreferNominalIdTypes
133
134
  from sarj_python_lint.rules.prefer_non_nullable_collection import (
134
135
  PreferNonNullableCollection,
@@ -298,6 +299,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
298
299
  PreferWalrusStreamLoop.id: PreferWalrusStreamLoop,
299
300
  PreferSelfTypeAnnotation.id: PreferSelfTypeAnnotation,
300
301
  PreferSetIsdisjoint.id: PreferSetIsdisjoint,
302
+ PreferNativeStringCheck.id: PreferNativeStringCheck,
301
303
  PreferSelfDocumentingConstant.id: PreferSelfDocumentingConstant,
302
304
  NoDuplicateDunderAllEntry.id: NoDuplicateDunderAllEntry,
303
305
  NoDunderAll.id: NoDunderAll,
@@ -41,17 +41,21 @@ class PreferInjectedDependencyOverMonkeypatch(Rule):
41
41
  code: str = "SARJ445"
42
42
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
43
43
  default_level=Severity.ERROR,
44
- summary="Tests should inject dependencies instead of replacing attributes through ambient patching.",
44
+ summary="Prefer explicit test dependencies; justify scoped attribute patches at the boundary.",
45
45
  rationale=(
46
46
  "Attribute patching hides collaborators and configuration behind ambient module or object state, coupling "
47
- "tests to lookup locations instead of an explicit boundary. Pytest monkeypatch remains appropriate for "
47
+ "tests to lookup locations instead of an explicit boundary. Replacing a scoped patch with bare assignment "
48
+ "does not fix that coupling and loses automatic restoration. Pytest monkeypatch remains appropriate for "
48
49
  "reversible process state such as environment variables and the working directory."
49
50
  ),
50
51
  remediation=(
51
52
  "Pass the collaborator, settings, clock, sleeper, random source, client, or factory explicitly; prefer a "
52
- "real in-process dependency, framework override, or purpose-built ABC/Protocol fake. If a runtime "
53
- "boundary is inherently global, or intercepting its lookup is the behavior under test, suppress SARJ445 "
54
- "at that call with the concrete reason an owned injection seam would invalidate the test."
53
+ "real in-process dependency, framework override, or purpose-built ABC/Protocol fake. A scoped patch may "
54
+ "replace an existing constructor-injected collaborator on a test-owned instance with an interface-conforming "
55
+ "fake when rebuilding the fixture would add wiring without improving the boundary. Keep attribute-existence "
56
+ "checks enabled and the tested implementation intact. Suppress SARJ445 only at that call, naming the "
57
+ "owned instance and injection seam. For inherently global boundaries or lookup interception, explain why "
58
+ "injection would invalidate the test. Do not switch to bare assignment merely to avoid this rule."
55
59
  ),
56
60
  category=RuleCategory.TESTING,
57
61
  autofix=AutofixPolicy.NONE,
@@ -61,6 +65,7 @@ class PreferInjectedDependencyOverMonkeypatch(Rule):
61
65
  "Environment, mapping, import-path, and working-directory mutations are intentionally allowed.",
62
66
  "Untyped handles are recognized only on pytest tests or fixtures; directly parametrized values are excluded.",
63
67
  "A nested function that captures a monkeypatch handle from an outer scope is not inferred.",
68
+ "Instance ownership and collaborator contracts are not inferred; justified scoped patches need a local suppression.",
64
69
  ),
65
70
  examples=(
66
71
  RuleExample(
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from collections import Counter
5
+ from pathlib import PurePosixPath
6
+ from typing import TYPE_CHECKING, ClassVar, 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
+ _PYDANTIC = frozenset({"pydantic"})
27
+ _BUILTINS = frozenset({"builtins"})
28
+
29
+
30
+ @final
31
+ class PreferNativeStringCheck(Rule):
32
+ id = "prefer-native-string-check"
33
+ code = "SARJ480"
34
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
35
+ default_level=Severity.WARNING,
36
+ summary="Use native string assertions instead of unconstrained Pydantic coercion.",
37
+ rationale="An unconstrained TypeAdapter(str).validate_python(...) obscures a simple string check and can decode bytes instead of rejecting them.",
38
+ remediation="Use isinstance(value, str) and the appropriate failure path; in tests, assert the type. Keep structured validation at the model or JSON boundary. Preserve intentional coercion or ValidationError contracts explicitly.",
39
+ category=RuleCategory.MAINTAINABILITY,
40
+ autofix=AutofixPolicy.NONE,
41
+ limitations=(
42
+ "Only plain built-in str adapters used by validate_python inside an assertion without validation options are reported. Configured adapters, constrained types, unions, models, containers, other scalar types and JSON/serialization operations are excluded.",
43
+ "Import aliases and uniquely assigned unconditional module-level adapter bindings are recognized. Local, conditional, reassigned, imported and indirect adapter values are not resolved; shadowed symbols are conservatively excluded.",
44
+ "Generated and vendored code are excluded. Intentional ValidationError or byte-decoding contracts need a local reasoned suppression; no automatic replacement can preserve those contracts.",
45
+ ),
46
+ examples=(
47
+ RuleExample(
48
+ example_id="adapter-for-string-check",
49
+ title="A string assertion unnecessarily constructs a schema validator",
50
+ outcome=ExampleOutcome.MATCH,
51
+ files=(
52
+ ExampleFile.python(
53
+ "tests/test_receipt.py",
54
+ 'from pydantic import TypeAdapter\ndef test_receipt():\n assert TypeAdapter(str).validate_python(receipt["id"]) == "abc"\n',
55
+ ),
56
+ ),
57
+ focus_path=PurePosixPath("tests/test_receipt.py"),
58
+ expected_count=1,
59
+ public=True,
60
+ ),
61
+ RuleExample(
62
+ example_id="native-string-assertion",
63
+ title="A native assertion rejects unexpected types and narrows the value",
64
+ outcome=ExampleOutcome.NO_MATCH,
65
+ files=(
66
+ ExampleFile.python(
67
+ "tests/test_receipt.py",
68
+ 'def test_receipt():\n value = receipt["id"]\n assert isinstance(value, str)\n assert value == "abc"\n',
69
+ ),
70
+ ),
71
+ focus_path=PurePosixPath("tests/test_receipt.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 (
82
+ context.generated
83
+ or not {"vendor", "vendored", "third_party"}.isdisjoint(context.path.parts)
84
+ or "validate_python" not in context.source
85
+ or context.tree is None
86
+ ):
87
+ return []
88
+ adapters = _module_adapters(context)
89
+ findings: list[Diagnostic] = []
90
+ for call in context.nodes(ast.Call):
91
+ if not isinstance(call.func, ast.Attribute) or call.func.attr != "validate_python" or call.keywords:
92
+ continue
93
+ if not _inside_assertion(call, context):
94
+ continue
95
+ receiver = call.func.value
96
+ if not (
97
+ _plain_string_adapter(receiver, context) or (isinstance(receiver, ast.Name) and receiver.id in adapters)
98
+ ):
99
+ continue
100
+ if is_suppressed(context.source_lines, call.lineno, self.code):
101
+ continue
102
+ findings.append(
103
+ Diagnostic(
104
+ path=context.path,
105
+ line=call.lineno,
106
+ col=call.col_offset + 1,
107
+ code=self.code,
108
+ severity=Severity.WARNING,
109
+ message="plain str adapter used only to validate a Python value; use isinstance(value, str) with an explicit failure path, or preserve the intentional Pydantic contract explicitly",
110
+ )
111
+ )
112
+ return sorted(findings, key=lambda finding: (finding.line, finding.col))
113
+
114
+
115
+ def _plain_string_adapter(node: ast.expr, context: PythonFileContext) -> bool:
116
+ if not isinstance(node, ast.Call):
117
+ return False
118
+ function = node.func.value if isinstance(node.func, ast.Subscript) else node.func
119
+ if not context.imports.resolves(function, sources=_PYDANTIC, symbol="TypeAdapter"):
120
+ return False
121
+ if len(node.args) == 1 and not node.keywords:
122
+ value = node.args[0]
123
+ elif not node.args and len(node.keywords) == 1 and node.keywords[0].arg == "type":
124
+ value = node.keywords[0].value
125
+ else:
126
+ return False
127
+ return (
128
+ isinstance(value, ast.Name) and value.id == "str" and context.imports.builtin_is_unshadowed("str")
129
+ ) or context.imports.resolves(value, sources=_BUILTINS, symbol="str")
130
+
131
+
132
+ def _module_adapters(context: PythonFileContext) -> frozenset[str]:
133
+ tree = context.tree
134
+ if tree is None:
135
+ return frozenset()
136
+ bindings = _binding_counts(context)
137
+ names: set[str] = set()
138
+ for statement in tree.body:
139
+ if isinstance(statement, ast.Assign) and len(statement.targets) == 1:
140
+ target, value = statement.targets[0], statement.value
141
+ elif isinstance(statement, ast.AnnAssign) and statement.value is not None:
142
+ target, value = statement.target, statement.value
143
+ else:
144
+ continue
145
+ if isinstance(target, ast.Name) and bindings[target.id] == 1 and _plain_string_adapter(value, context):
146
+ names.add(target.id)
147
+ return frozenset(names)
148
+
149
+
150
+ def _binding_counts(context: PythonFileContext) -> Counter[str]:
151
+ bindings = Counter(node.id for node in context.nodes(ast.Name) if isinstance(node.ctx, ast.Store | ast.Del))
152
+ bindings.update(argument.arg for argument in context.nodes(ast.arg))
153
+ bindings.update(alias.asname or alias.name.split(".")[0] for alias in context.nodes(ast.alias))
154
+ bindings.update(node.name for node in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
155
+ bindings.update(node.name for node in context.nodes(ast.ExceptHandler) if node.name is not None)
156
+ bindings.update(node.name for node in context.nodes(ast.MatchAs, ast.MatchStar) if node.name is not None)
157
+ bindings.update(node.rest for node in context.nodes(ast.MatchMapping) if node.rest is not None)
158
+ return bindings
159
+
160
+
161
+ def _inside_assertion(call: ast.Call, context: PythonFileContext) -> bool:
162
+ parent = context.parents.get(call)
163
+ while parent is not None:
164
+ if isinstance(parent, ast.Assert):
165
+ return True
166
+ if isinstance(parent, (ast.Lambda, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
167
+ return False
168
+ parent = context.parents.get(parent)
169
+ return False