sarj-python-lint 1.7.4__tar.gz → 1.7.6__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 (163) hide show
  1. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/pyproject.toml +1 -1
  3. sarj_python_lint-1.7.6/src/sarj_python_lint/rules/_contract_annotation.py +45 -0
  4. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +2 -1
  5. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +49 -20
  6. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_explicit_service_contract.py +5 -10
  7. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_public_dependency_contract.py +15 -14
  8. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/.gitignore +0 -0
  9. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/LICENSE +0 -0
  10. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/README.md +0 -0
  11. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/__init__.py +0 -0
  12. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/__main__.py +0 -0
  13. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_analysis_session.py +0 -0
  14. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_file_context.py +0 -0
  15. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_filesystem.py +0 -0
  16. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/_version.py +0 -0
  20. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/benchmark.py +0 -0
  21. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/json_boundary.py +0 -0
  22. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/py.typed +0 -0
  23. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/ratchet.py +0 -0
  24. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rule_base.py +0 -0
  25. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/__init__.py +0 -0
  26. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  27. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  28. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  29. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_comments.py +0 -0
  30. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  31. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  32. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  33. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_first_party.py +0 -0
  34. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  35. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_imports.py +0 -0
  36. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_logging.py +0 -0
  37. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  38. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_paths.py +0 -0
  39. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_project_index.py +0 -0
  40. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  41. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_pytest.py +0 -0
  42. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_registry.py +0 -0
  43. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  44. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  45. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_sql.py +0 -0
  46. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  47. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  48. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  49. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  50. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  51. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  52. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  53. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  54. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  55. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  56. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  57. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  58. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  59. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  60. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  61. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  62. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  63. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  64. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  65. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  66. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  67. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  68. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  69. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  70. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  71. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  72. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  73. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  74. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  75. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  76. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  77. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  78. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  79. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  80. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  81. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  82. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  83. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  84. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  85. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  86. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  87. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  88. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  89. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  90. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  91. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  92. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  93. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  94. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  95. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  96. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  97. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  98. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  99. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  100. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  101. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  102. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  103. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  104. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  105. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  106. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  107. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  108. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  109. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  110. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  111. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  112. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  113. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  114. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  115. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  116. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  117. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  118. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  119. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  120. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  121. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  122. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  123. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  124. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  125. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  126. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  127. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  128. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  129. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  130. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  131. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  132. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  133. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  134. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  135. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  136. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  137. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  138. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  139. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  140. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  141. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  142. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  143. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  144. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  145. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  146. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  147. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  148. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  149. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  150. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  151. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  152. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  153. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  154. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/stepdown.py +0 -0
  155. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  156. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  157. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  158. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  159. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  160. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  161. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  162. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  163. {sarj_python_lint-1.7.4 → sarj_python_lint-1.7.6}/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.7.4
3
+ Version: 1.7.6
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.7.4"
3
+ version = "1.7.6"
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" }]
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+
5
+
6
+ def annotation_reference(annotation: ast.expr) -> ast.Name | ast.Attribute | None:
7
+ match annotation:
8
+ case ast.Name() | ast.Attribute():
9
+ return annotation
10
+ case ast.Constant(value=str() as value):
11
+ try:
12
+ return annotation_reference(ast.parse(value, mode="eval").body)
13
+ except SyntaxError, ValueError:
14
+ return None
15
+ case ast.BinOp(left=left, op=ast.BitOr(), right=right):
16
+ if isinstance(left, ast.Constant) and left.value is None:
17
+ return annotation_reference(right)
18
+ if isinstance(right, ast.Constant) and right.value is None:
19
+ return annotation_reference(left)
20
+ case ast.Subscript(value=value, slice=inner):
21
+ if (wrapped := _nullable_inner(value, inner)) is not None:
22
+ return annotation_reference(wrapped)
23
+ case _:
24
+ return None
25
+ return None
26
+
27
+
28
+ def _nullable_inner(wrapper: ast.expr, inner: ast.expr) -> ast.expr | None:
29
+ name = _tail(wrapper)
30
+ if name == "Optional":
31
+ return inner
32
+ if name == "Annotated" and isinstance(inner, ast.Tuple) and inner.elts:
33
+ return inner.elts[0]
34
+ if name != "Union" or not isinstance(inner, ast.Tuple):
35
+ return None
36
+ substantive = [item for item in inner.elts if not isinstance(item, ast.Constant) or item.value is not None]
37
+ return substantive[0] if len(substantive) == 1 else None
38
+
39
+
40
+ def _tail(node: ast.expr) -> str | None:
41
+ match node:
42
+ case ast.Name(id=name) | ast.Attribute(attr=name):
43
+ return name
44
+ case _:
45
+ return None
@@ -52,7 +52,7 @@ class PreferSetIsdisjoint(ProjectRule):
52
52
  id = "prefer-set-isdisjoint"
53
53
  code = "SARJ431"
54
54
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
55
- default_level=Severity.WARNING,
55
+ default_level=Severity.ERROR,
56
56
  summary="Prefer `set.isdisjoint` when a built-in set intersection is used only as a boolean predicate.",
57
57
  rationale="`isdisjoint` names the overlap predicate directly and avoids allocating an intersection that is immediately discarded.",
58
58
  remediation="Use `left.isdisjoint(right)` and negate it when the condition requires overlap.",
@@ -70,6 +70,7 @@ class PreferSetIsdisjoint(ProjectRule):
70
70
  title="A declared set field is used only for an overlap test",
71
71
  outcome=ExampleOutcome.MATCH,
72
72
  files=(
73
+ ExampleFile(PurePosixPath("pyproject.toml"), '[project]\nname = "example"\nversion = "0.1.0"\n'),
73
74
  ExampleFile.python(
74
75
  "app/policy.py",
75
76
  "class AccessCase:\n tags: frozenset[str]\n\ndef accepts(case: AccessCase):\n if case.tags & {'read', 'write'}:\n allow()\n",
@@ -16,8 +16,8 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._contract_annotation import annotation_reference
19
20
  from sarj_python_lint.rules._imports import ABC_SOURCES, TYPING_SOURCES, ImportIndex
20
- from sarj_python_lint.rules.require_port_for_service import annotation_tail
21
21
 
22
22
 
23
23
  if TYPE_CHECKING:
@@ -41,7 +41,7 @@ class RequireExplicitContractImplementation(ProjectRule):
41
41
  autofix=AutofixPolicy.NONE,
42
42
  limitations=(
43
43
  "Only direct constructor arguments and unreassigned local constructor results supplied to a resolvable typed parameter are checked.",
44
- "Dynamic factories, containers, casts, unresolved ancestry, and third-party implementations are excluded.",
44
+ "Dynamic factories, containers, casts, unresolved or multiple owned ABC ancestry, and third-party implementations are excluded.",
45
45
  ),
46
46
  examples=(
47
47
  RuleExample(
@@ -238,10 +238,10 @@ class _InjectionVisitor(ast.NodeVisitor):
238
238
  def _check_argument(self, parameter: ast.arg | None, value: ast.expr, target_unit: SourceUnit) -> None:
239
239
  if parameter is None or parameter.annotation is None:
240
240
  return
241
- name = annotation_tail(parameter.annotation)
242
- if name is None:
241
+ reference = annotation_reference(parameter.annotation)
242
+ if reference is None:
243
243
  return
244
- contract = self.project.class_for(target_unit, ast.Name(id=name, ctx=ast.Load()))
244
+ contract = self.project.class_for(target_unit, reference)
245
245
  if contract is None or not _is_direct_contract(contract, self.project):
246
246
  return
247
247
  origin = self._origin(value)
@@ -277,9 +277,6 @@ def _is_direct_contract(summary: ClassSummary, project: ProjectIndexSet) -> bool
277
277
  (base.module, base.name) in {("typing", "Protocol"), ("typing_extensions", "Protocol")}
278
278
  for base in summary.bases
279
279
  )
280
- abc = any((base.module, base.name) == ("abc", "ABC") for base in summary.bases)
281
- if not protocol and not abc:
282
- return False
283
280
  unit = project.source_unit(summary.symbol.module)
284
281
  if unit is None or unit.tree is None:
285
282
  return False
@@ -289,19 +286,51 @@ def _is_direct_contract(summary: ClassSummary, project: ProjectIndexSet) -> bool
289
286
  )
290
287
  if declaration is None:
291
288
  return False
292
- imports = ImportIndex.from_tree(unit.tree, module_scope_only=True)
293
- return any(
294
- isinstance(member, ast.FunctionDef | ast.AsyncFunctionDef)
295
- and not member.name.startswith("_")
296
- and (
297
- protocol
298
- or any(
299
- imports.resolves(decorator, sources=ABC_SOURCES, symbol="abstractmethod")
300
- for decorator in member.decorator_list
301
- )
302
- )
289
+ if protocol and any(
290
+ isinstance(member, ast.FunctionDef | ast.AsyncFunctionDef) and not member.name.startswith("_")
303
291
  for member in declaration.body
292
+ ):
293
+ return True
294
+ if not project.class_inherits_from(unit, summary.symbol.name, frozenset({"abc.ABC"})):
295
+ return False
296
+ return bool(_remaining_abstract_operations(summary, project, set()))
297
+
298
+
299
+ def _remaining_abstract_operations(summary: ClassSummary, project: ProjectIndexSet, seen: set[SymbolRef]) -> set[str]:
300
+ if summary.symbol in seen:
301
+ return set()
302
+ seen.add(summary.symbol)
303
+ unit = project.source_unit(summary.symbol.module)
304
+ if unit is None or unit.tree is None:
305
+ return set()
306
+ declaration = next(
307
+ (item for item in unit.tree.body if isinstance(item, ast.ClassDef) and item.name == summary.symbol.name),
308
+ None,
304
309
  )
310
+ if declaration is None:
311
+ return set()
312
+ owned_parents = [base for base in summary.bases if project.source_unit(base.module) is not None]
313
+ if len(owned_parents) > 1:
314
+ return set()
315
+ abstract: set[str] = set()
316
+ for base in summary.bases:
317
+ parent_unit = project.source_unit(base.module)
318
+ parent = project.class_for(parent_unit, ast.Name(id=base.name, ctx=ast.Load())) if parent_unit else None
319
+ if parent is not None:
320
+ abstract.update(_remaining_abstract_operations(parent, project, seen.copy()))
321
+ imports = ImportIndex.from_tree(unit.tree, module_scope_only=True)
322
+ _apply_abstract_overrides(abstract, declaration, imports)
323
+ return abstract
324
+
325
+
326
+ def _apply_abstract_overrides(abstract: set[str], declaration: ast.ClassDef, imports: ImportIndex) -> None:
327
+ for member in declaration.body:
328
+ if not isinstance(member, ast.FunctionDef | ast.AsyncFunctionDef) or member.name.startswith("_"):
329
+ continue
330
+ if any(imports.resolves(dec, sources=ABC_SOURCES, symbol="abstractmethod") for dec in member.decorator_list):
331
+ abstract.add(member.name)
332
+ else:
333
+ abstract.discard(member.name)
305
334
 
306
335
 
307
336
  def _inherits_contract(
@@ -326,7 +355,7 @@ def _inherits_contract(
326
355
  if ancestor is None:
327
356
  unknown = True
328
357
  continue
329
- relationship = _inherits_contract(ancestor, target, project, seen)
358
+ relationship = _inherits_contract(ancestor, target, project, seen.copy())
330
359
  if relationship is True:
331
360
  return True
332
361
  unknown |= relationship is None
@@ -260,7 +260,7 @@ def _contract_status(
260
260
  contract = _contract_surface(declaration, source_unit, source_imports, project, set())
261
261
  surfaces.update(contract.operations)
262
262
  unknown |= contract.unresolved
263
- return True if operations.intersection(surfaces) else None if unknown else False
263
+ return True if operations <= surfaces else None if unknown else False
264
264
 
265
265
 
266
266
  def _resolve_base(
@@ -311,7 +311,7 @@ def _contract_surface(
311
311
  return _ContractSurface(operations=frozenset(), unresolved=True)
312
312
  seen.add(symbol)
313
313
  abc = any(imports.resolves(base, sources=ABC_SOURCES, symbol="ABC") for base in declaration.bases)
314
- surface: set[str] = _abstract_operations(declaration, imports) if abc else set()
314
+ surface: set[str] = _public_operations(declaration) if abc else set()
315
315
  unknown = False
316
316
  local_classes = (
317
317
  {item.name: item for item in unit.tree.body if isinstance(item, ast.ClassDef)}
@@ -328,16 +328,11 @@ def _contract_surface(
328
328
  unknown = True
329
329
  continue
330
330
  ancestor, ancestor_unit, ancestor_imports = resolved
331
- ancestor_surface = _contract_surface(ancestor, ancestor_unit, ancestor_imports, project, seen)
331
+ ancestor_surface = _contract_surface(ancestor, ancestor_unit, ancestor_imports, project, seen.copy())
332
332
  surface.update(ancestor_surface.operations)
333
333
  unknown |= ancestor_surface.unresolved
334
334
  return _ContractSurface(operations=frozenset(surface), unresolved=unknown)
335
335
 
336
336
 
337
- def _abstract_operations(declaration: ast.ClassDef, imports: ImportIndex) -> set[str]:
338
- return {
339
- method.name
340
- for method in class_methods(declaration)
341
- if not method.name.startswith("_")
342
- and any(imports.resolves(dec, sources=ABC_SOURCES, symbol="abstractmethod") for dec in method.decorator_list)
343
- }
337
+ def _public_operations(declaration: ast.ClassDef) -> set[str]:
338
+ return {method.name for method in class_methods(declaration) if not method.name.startswith("_")}
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._contract_annotation import annotation_reference
19
20
  from sarj_python_lint.rules._imports import ABC_SOURCES, TYPING_SOURCES, ImportIndex
20
21
  from sarj_python_lint.rules._paths import is_test_path
21
22
  from sarj_python_lint.rules.require_explicit_service_contract import (
@@ -24,7 +25,6 @@ from sarj_python_lint.rules.require_explicit_service_contract import (
24
25
  operations_for_fields,
25
26
  )
26
27
  from sarj_python_lint.rules.require_port_for_service import (
27
- annotation_tail,
28
28
  class_methods,
29
29
  is_data_type,
30
30
  self_stored_parameters,
@@ -136,10 +136,10 @@ class RequirePublicDependencyContract(ProjectRule):
136
136
  fields = fields_by_parameter.get(parameter.arg)
137
137
  if fields is None or parameter.annotation is None or not operations_for_fields(node, fields):
138
138
  return None
139
- annotation_name = annotation_tail(parameter.annotation)
140
- if annotation_name is None or annotation_name in {"Callable", "Awaitable", "Coroutine"}:
139
+ reference = annotation_reference(parameter.annotation)
140
+ if reference is None:
141
141
  return None
142
- reason = _hidden_dependency(annotation_name, classes, context)
142
+ reason = _hidden_dependency(reference, classes, context)
143
143
  if reason is None or is_suppressed(context.source_lines, parameter.lineno, self.code):
144
144
  return None
145
145
  return Diagnostic(
@@ -152,9 +152,16 @@ class RequirePublicDependencyContract(ProjectRule):
152
152
  )
153
153
 
154
154
 
155
- def _hidden_dependency(name: str, classes: dict[str, ast.ClassDef], context: PythonFileContext) -> str | None:
156
- if name.startswith("_") and name in classes and not is_data_type(classes[name]):
157
- return f"private local contract `{name}`"
155
+ def _hidden_dependency(
156
+ reference: ast.Name | ast.Attribute, classes: dict[str, ast.ClassDef], context: PythonFileContext
157
+ ) -> str | None:
158
+ if (
159
+ isinstance(reference, ast.Name)
160
+ and reference.id.startswith("_")
161
+ and reference.id in classes
162
+ and not is_data_type(classes[reference.id])
163
+ ):
164
+ return f"private local contract `{reference.id}`"
158
165
  project = context.session.project
159
166
  if project is None:
160
167
  return None
@@ -162,15 +169,9 @@ def _hidden_dependency(name: str, classes: dict[str, ast.ClassDef], context: Pyt
162
169
  source_unit = project.unit(context.path)
163
170
  if source_unit is None:
164
171
  return None
165
- annotation = ast.Name(id=name, ctx=ast.Load())
166
- summary = project.class_for(source_unit, annotation)
172
+ summary = project.class_for(source_unit, reference)
167
173
  if summary is None:
168
174
  return None
169
- if (
170
- name not in classes
171
- and context.imports.resolved_qualified_name(annotation) != f"{summary.symbol.module}.{summary.symbol.name}"
172
- ):
173
- return None
174
175
  declaration_unit = project.source_unit(summary.symbol.module)
175
176
  if declaration_unit is None or declaration_unit.tree is None:
176
177
  return None