sarj-python-lint 1.20.2__tar.gz → 1.21.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 (178) hide show
  1. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +211 -19
  4. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/.gitignore +0 -0
  5. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/LICENSE +0 -0
  6. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/README.md +0 -0
  7. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/__init__.py +0 -0
  8. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/__main__.py +0 -0
  9. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  10. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_file_context.py +0 -0
  11. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_filesystem.py +0 -0
  12. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_python_target.py +0 -0
  13. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  14. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_secret_names.py +0 -0
  15. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/_version.py +0 -0
  16. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/benchmark.py +0 -0
  17. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/json_boundary.py +0 -0
  18. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/py.typed +0 -0
  19. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/ratchet.py +0 -0
  20. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rule_base.py +0 -0
  21. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  22. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  23. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  24. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  25. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  26. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  27. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  28. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  29. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  30. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  31. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  32. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  33. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  34. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  35. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  36. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  37. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  38. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  39. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  40. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  41. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  42. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  43. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  44. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  45. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  46. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  47. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  48. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  49. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  50. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  51. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  52. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  53. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  54. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  55. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  56. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  57. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  58. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  59. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  60. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  61. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  62. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  63. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  64. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  65. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  66. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  67. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  68. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  69. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  70. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  71. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  72. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  73. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  74. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  75. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  76. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  77. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  78. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  79. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  80. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  81. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  82. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  83. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  84. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  85. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  86. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  87. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  88. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  89. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  90. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  91. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  92. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  93. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  94. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  95. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  96. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  97. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  98. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  99. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  100. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  101. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  102. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  103. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  104. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  105. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  106. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  107. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  108. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  109. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  110. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  111. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  112. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  113. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  114. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  115. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  116. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  117. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  118. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  119. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  120. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  121. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  122. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  123. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  124. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  125. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  126. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  127. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  128. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  129. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  130. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  131. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  132. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  133. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  134. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  135. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  136. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  137. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  138. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  139. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  140. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  141. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  142. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  143. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  144. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  145. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  146. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  147. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  148. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  149. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  150. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  151. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  152. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  153. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  154. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  155. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  156. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  157. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  158. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  159. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  160. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  161. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  162. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  163. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  164. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  165. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  166. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  167. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  168. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  169. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  170. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  171. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  172. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  173. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  174. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  175. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  176. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  177. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  178. {sarj_python_lint-1.20.2 → sarj_python_lint-1.21.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.20.2
3
+ Version: 1.21.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.20.2"
3
+ version = "1.21.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" }]
@@ -29,7 +29,10 @@ _PYDANTIC = frozenset({"pydantic", "pydantic.type_adapter"})
29
29
  _BUILTINS = frozenset({"builtins"})
30
30
  _PYTEST = frozenset({"pytest"})
31
31
  _VALIDATION_ERROR = frozenset({"pydantic", "pydantic_core"})
32
- _MODULES = _PYDANTIC | _BUILTINS | _PYTEST | _VALIDATION_ERROR
32
+ _JSON_TYPES = frozenset({"pydantic", "pydantic.types"})
33
+ _ENCODERS = frozenset({"fastapi.encoders"})
34
+ _MODULES = _PYDANTIC | _BUILTINS | _PYTEST | _VALIDATION_ERROR | _JSON_TYPES | _ENCODERS | {"fastapi"}
35
+ _MAPPING_SCHEMA_ARGUMENTS = 2
33
36
 
34
37
 
35
38
  @dataclass(frozen=True, slots=True)
@@ -44,15 +47,15 @@ class PreferNativeStringCheck(Rule):
44
47
  code = "SARJ480"
45
48
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
46
49
  default_level=Severity.WARNING,
47
- summary="Use native string assertions instead of unconstrained Pydantic coercion.",
48
- rationale="An unconstrained TypeAdapter(str).validate_python(...) obscures a simple string check and can decode bytes instead of rejecting them.",
49
- 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.",
50
+ summary="Avoid unconstrained adapters for string assertions or already encoded JSON mappings.",
51
+ rationale="A plain string adapter obscures an assertion and can decode bytes. Generic JSON-dictionary validation after encoding adds a separate runtime contract, often only to satisfy an annotation; it can reject nested integer keys that a response serializer accepts and does not prove response serializability.",
52
+ remediation="Use isinstance(value, str) for string assertions. For annotation-only JSON adaptation, return a typed Response/JSONResponse at the serialization boundary. Keep structured input and specific schema validation. Preserve intentional coercion, recursive string-key validation or ValidationError contracts explicitly.",
50
53
  category=RuleCategory.MAINTAINABILITY,
51
54
  autofix=AutofixPolicy.NONE,
52
55
  limitations=(
53
- "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.",
56
+ "Plain built-in str adapters are checked only inside assertions. The JSON branch checks exactly dict[str, pydantic.JsonValue] immediately wrapping FastAPI jsonable_encoder of a dictionary literal with string literal keys; dynamic values and keyword encoder options are allowed. Custom encoders must be a literal mapping with only proven built-in bytes keys, so they cannot replace the root mapping or its string keys. Unknown input shapes, opaque or shape-changing encoders, schema aliases, other containers, constrained types, unions, models, configured adapters and validation options are excluded.",
54
57
  "Scoped import aliases and unescaped unconditional module or local adapter bindings are recognized, including independent functions reusing a name. Conditional, reassigned, indirect or class/comprehension/parameter/global/nonlocal cached bindings are excluded, as are shadowed symbols, wildcard imports and imports with visible mutation or escape.",
55
- "Only assertion tests are checked: messages and lambda or generator expressions are excluded. Proven pytest.raises(ValidationError) contexts are preserved; other intentional byte-decoding or exception contracts need a local reasoned suppression. No automatic replacement can preserve those contracts.",
58
+ "String assertion messages and deferred expressions are excluded. The JSON branch excludes lambdas, comprehensions, function/class headers and cached encoded values; proven pytest.raises(ValidationError) contexts and try bodies catching ValidationError are preserved. Broader catches and contextlib.suppress are not inferred. This advisory does not prove equivalent behavior: intentional validation or byte-decoding contracts need a local reasoned suppression; there is no autofix.",
56
59
  ),
57
60
  examples=(
58
61
  RuleExample(
@@ -83,6 +86,36 @@ class PreferNativeStringCheck(Rule):
83
86
  expected_count=0,
84
87
  public=True,
85
88
  ),
89
+ RuleExample(
90
+ example_id="generic-json-after-encoding",
91
+ scenario="json-output",
92
+ title="A generic validator wraps an already encoded response mapping",
93
+ outcome=ExampleOutcome.MATCH,
94
+ files=(
95
+ ExampleFile.python(
96
+ "app/responses.py",
97
+ "from pydantic import TypeAdapter, JsonValue\nfrom fastapi.encoders import jsonable_encoder\ndef response_content(body):\n return TypeAdapter(dict[str, JsonValue]).validate_python(jsonable_encoder({'body': body}, custom_encoder={bytes: lambda value: value.decode('utf-8', errors='replace')}))\n",
98
+ ),
99
+ ),
100
+ focus_path=PurePosixPath("app/responses.py"),
101
+ expected_count=1,
102
+ public=True,
103
+ ),
104
+ RuleExample(
105
+ example_id="response-serialization-boundary",
106
+ scenario="json-output",
107
+ title="The response owns encoded content without annotation-only validation",
108
+ outcome=ExampleOutcome.NO_MATCH,
109
+ files=(
110
+ ExampleFile.python(
111
+ "app/responses.py",
112
+ "from fastapi.encoders import jsonable_encoder\nfrom fastapi.responses import JSONResponse\ndef response(body) -> JSONResponse:\n return JSONResponse(content=jsonable_encoder({'body': body}))\n",
113
+ ),
114
+ ),
115
+ focus_path=PurePosixPath("app/responses.py"),
116
+ expected_count=0,
117
+ public=True,
118
+ ),
86
119
  ),
87
120
  )
88
121
  description = documentation.summary
@@ -109,13 +142,24 @@ class PreferNativeStringCheck(Rule):
109
142
  receiver = _validation_receiver(call)
110
143
  if receiver is None:
111
144
  continue
112
- if not _inside_assertion(call, context) or _expects_validation_error(
113
- call, context, provenance, imports, unsafe_imports
114
- ):
115
- continue
116
145
  if isinstance(receiver, ast.Name):
117
146
  receiver = _cached_constructor(receiver, call, provenance, uncertain_names, mutated_bindings)
118
- if receiver is None or not _plain_string_adapter(receiver, context, provenance, imports, unsafe_imports):
147
+ if receiver is None:
148
+ continue
149
+ if _inside_assertion(call, context) and _plain_string_adapter(
150
+ receiver, context, provenance, imports, unsafe_imports
151
+ ):
152
+ message = "plain str adapter in an assertion; use isinstance(value, str) with an explicit failure path, or preserve the intentional Pydantic contract explicitly"
153
+ elif (
154
+ _encoded_json_mapping(call.args[0], context, provenance, imports, unsafe_imports)
155
+ and _supported_json_context(call, context)
156
+ and _plain_json_mapping_adapter(receiver, context, provenance, imports, unsafe_imports)
157
+ and not _catches_validation_error(call, context, provenance, imports, unsafe_imports)
158
+ ):
159
+ message = "generic JSON validation after encoding; for annotation-only adaptation, return a typed Response/JSONResponse with the encoded mapping, or preserve intentional recursive string-key/ValidationError validation explicitly"
160
+ else:
161
+ continue
162
+ if _expects_validation_error(call, context, provenance, imports, unsafe_imports):
119
163
  continue
120
164
  if is_suppressed(context.source_lines, call.lineno, self.code):
121
165
  continue
@@ -126,7 +170,7 @@ class PreferNativeStringCheck(Rule):
126
170
  col=call.col_offset + 1,
127
171
  code=self.code,
128
172
  severity=Severity.WARNING,
129
- message="plain str adapter in an assertion; use isinstance(value, str) with an explicit failure path, or preserve the intentional Pydantic contract explicitly",
173
+ message=message,
130
174
  )
131
175
  )
132
176
  return sorted(findings, key=lambda finding: (finding.line, finding.col))
@@ -215,6 +259,130 @@ def _single_value(call: ast.Call, keyword: str) -> ast.expr | None:
215
259
  return None
216
260
 
217
261
 
262
+ def _plain_json_mapping_adapter(
263
+ node: ast.expr,
264
+ context: PythonFileContext,
265
+ provenance: ResourceProvenance,
266
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
267
+ unsafe_imports: set[str],
268
+ ) -> bool:
269
+ if not isinstance(node, ast.Call):
270
+ return False
271
+ function = node.func.value if isinstance(node.func, ast.Subscript) else node.func
272
+ schema = _single_value(node, "type")
273
+ if (
274
+ not isinstance(schema, ast.Subscript)
275
+ or not isinstance(schema.slice, ast.Tuple)
276
+ or len(schema.slice.elts) != _MAPPING_SCHEMA_ARGUMENTS
277
+ or not _resolves(
278
+ function, context, provenance, imports, unsafe_imports, sources=_PYDANTIC, symbol="TypeAdapter"
279
+ )
280
+ ):
281
+ return False
282
+ key, value = schema.slice.elts
283
+ return (
284
+ _builtin_type(schema.value, context, provenance, imports, unsafe_imports, symbol="dict")
285
+ and _builtin_type(key, context, provenance, imports, unsafe_imports, symbol="str")
286
+ and _resolves(value, context, provenance, imports, unsafe_imports, sources=_JSON_TYPES, symbol="JsonValue")
287
+ )
288
+
289
+
290
+ def _builtin_type(
291
+ node: ast.expr,
292
+ context: PythonFileContext,
293
+ provenance: ResourceProvenance,
294
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
295
+ unsafe_imports: set[str],
296
+ *,
297
+ symbol: str,
298
+ ) -> bool:
299
+ return (
300
+ isinstance(node, ast.Name)
301
+ and node.id == symbol
302
+ and context.imports.builtin_is_unshadowed(symbol)
303
+ and _lexical_binding(symbol, node, provenance) is None
304
+ and "builtins" not in unsafe_imports
305
+ and f"builtins.{symbol}" not in unsafe_imports
306
+ ) or _resolves(node, context, provenance, imports, unsafe_imports, sources=_BUILTINS, symbol=symbol)
307
+
308
+
309
+ def _encoded_json_mapping(
310
+ node: ast.expr,
311
+ context: PythonFileContext,
312
+ provenance: ResourceProvenance,
313
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
314
+ unsafe_imports: set[str],
315
+ ) -> bool:
316
+ if (
317
+ not isinstance(node, ast.Call)
318
+ or not _resolves(
319
+ node.func, context, provenance, imports, unsafe_imports, sources=_ENCODERS, symbol="jsonable_encoder"
320
+ )
321
+ or any(keyword.arg is None for keyword in node.keywords)
322
+ ):
323
+ return False
324
+ custom_encoder = next((keyword.value for keyword in node.keywords if keyword.arg == "custom_encoder"), None)
325
+ if custom_encoder is not None and not _bytes_encoders(custom_encoder, context, provenance, imports, unsafe_imports):
326
+ return False
327
+ value = (
328
+ node.args[0]
329
+ if len(node.args) == 1 and not any(keyword.arg == "obj" for keyword in node.keywords)
330
+ else next((keyword.value for keyword in node.keywords if not node.args and keyword.arg == "obj"), None)
331
+ )
332
+ return isinstance(value, ast.Dict) and all(
333
+ isinstance(key, ast.Constant) and isinstance(key.value, str) for key in value.keys
334
+ )
335
+
336
+
337
+ def _bytes_encoders(
338
+ node: ast.expr,
339
+ context: PythonFileContext,
340
+ provenance: ResourceProvenance,
341
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
342
+ unsafe_imports: set[str],
343
+ ) -> bool:
344
+ return isinstance(node, ast.Dict) and all(
345
+ key is not None and _builtin_type(key, context, provenance, imports, unsafe_imports, symbol="bytes")
346
+ for key in node.keys
347
+ )
348
+
349
+
350
+ def _supported_json_context(call: ast.Call, context: PythonFileContext) -> bool:
351
+ child: ast.AST = call
352
+ parent = context.parents.get(child)
353
+ while parent is not None:
354
+ if isinstance(parent, ast.Lambda | ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp):
355
+ return False
356
+ if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
357
+ return isinstance(child, ast.stmt)
358
+ child = parent
359
+ parent = context.parents.get(child)
360
+ return True
361
+
362
+
363
+ def _catches_validation_error(
364
+ call: ast.Call,
365
+ context: PythonFileContext,
366
+ provenance: ResourceProvenance,
367
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
368
+ unsafe_imports: set[str],
369
+ ) -> bool:
370
+ child: ast.AST = call
371
+ parent = context.parents.get(child)
372
+ while parent is not None and not isinstance(
373
+ parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda | ast.GeneratorExp
374
+ ):
375
+ if isinstance(parent, ast.Try | ast.TryStar) and child in parent.body:
376
+ for handler in parent.handlers:
377
+ if handler.type is not None and _validation_error_type(
378
+ handler.type, context, provenance, imports, unsafe_imports
379
+ ):
380
+ return True
381
+ child = parent
382
+ parent = context.parents.get(child)
383
+ return False
384
+
385
+
218
386
  def _resolves(
219
387
  node: ast.expr,
220
388
  context: PythonFileContext,
@@ -256,6 +424,8 @@ def _import_key(module: str, symbol: str | None) -> str:
256
424
  return f"pydantic.{symbol}"
257
425
  if symbol == "ValidationError" and module in _VALIDATION_ERROR:
258
426
  return f"pydantic_core.{symbol}"
427
+ if symbol == "JsonValue" and module in _JSON_TYPES:
428
+ return f"pydantic.{symbol}"
259
429
  return f"{module}.{symbol}" if symbol is not None else module
260
430
 
261
431
 
@@ -310,6 +480,10 @@ def _unsafe_imports(
310
480
  unsafe.add(key)
311
481
  if key in _PYDANTIC:
312
482
  unsafe.add("pydantic.TypeAdapter")
483
+ if key in _JSON_TYPES:
484
+ unsafe.add("pydantic.JsonValue")
485
+ if key in _ENCODERS or key == "fastapi":
486
+ unsafe.add("fastapi.encoders.jsonable_encoder")
313
487
  if key in _VALIDATION_ERROR:
314
488
  unsafe.add("pydantic_core.ValidationError")
315
489
  if key == "pytest":
@@ -371,7 +545,7 @@ def _import_escapes(
371
545
  imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
372
546
  ) -> bool:
373
547
  parent = context.parents.get(read)
374
- if key == "pydantic.TypeAdapter":
548
+ if key in {"pydantic.TypeAdapter", "fastapi.encoders.jsonable_encoder"}:
375
549
  return not (
376
550
  isinstance(parent, ast.Attribute)
377
551
  or (isinstance(parent, ast.Subscript) and parent.value is read)
@@ -446,14 +620,22 @@ def _expects_validation_error(
446
620
  imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
447
621
  unsafe_imports: set[str],
448
622
  ) -> bool:
449
- parent = context.parents.get(call)
450
- while parent is not None and not isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
451
- if isinstance(parent, ast.With | ast.AsyncWith) and any(
452
- _raises_validation_error(item.context_expr, context, provenance, imports, unsafe_imports)
453
- for item in parent.items
623
+ child: ast.AST = call
624
+ parent = context.parents.get(child)
625
+ while parent is not None and not isinstance(
626
+ parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda | ast.GeneratorExp
627
+ ):
628
+ if (
629
+ isinstance(parent, ast.With | ast.AsyncWith)
630
+ and isinstance(child, ast.stmt)
631
+ and any(
632
+ _raises_validation_error(item.context_expr, context, provenance, imports, unsafe_imports)
633
+ for item in parent.items
634
+ )
454
635
  ):
455
636
  return True
456
- parent = context.parents.get(parent)
637
+ child = parent
638
+ parent = context.parents.get(child)
457
639
  return False
458
640
 
459
641
 
@@ -477,6 +659,16 @@ def _raises_validation_error(
477
659
  )
478
660
  if expected is None:
479
661
  return False
662
+ return _validation_error_type(expected, context, provenance, imports, unsafe_imports)
663
+
664
+
665
+ def _validation_error_type(
666
+ expected: ast.expr,
667
+ context: PythonFileContext,
668
+ provenance: ResourceProvenance,
669
+ imports: dict[ast.Import | ast.ImportFrom, ImportIndex],
670
+ unsafe_imports: set[str],
671
+ ) -> bool:
480
672
  types = expected.elts if isinstance(expected, ast.Tuple) else (expected,)
481
673
  return any(
482
674
  _resolves(