sarj-python-lint 1.13.0__tar.gz → 1.14.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 (170) hide show
  1. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +48 -8
  5. sarj_python_lint-1.14.1/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +326 -0
  6. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/.gitignore +0 -0
  7. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/LICENSE +0 -0
  8. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/README.md +0 -0
  9. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/_version.py +0 -0
  18. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/benchmark.py +0 -0
  19. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/json_boundary.py +0 -0
  20. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/py.typed +0 -0
  21. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/ratchet.py +0 -0
  22. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rule_base.py +0 -0
  23. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  24. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  25. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  26. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  27. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  28. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  29. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  30. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  31. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  32. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  33. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  34. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  35. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  36. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  37. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  38. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  39. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  40. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  41. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  42. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  43. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  44. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  45. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  46. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  47. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  48. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  49. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  50. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  51. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
  52. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  53. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  54. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  55. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  56. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  57. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  58. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  59. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  60. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  61. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  62. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  63. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  64. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  65. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  66. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  67. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  68. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  69. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  70. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  71. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  72. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  73. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  74. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  75. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  76. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  77. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  78. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  79. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  80. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  81. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  82. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  83. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  84. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  85. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  86. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  87. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  88. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  89. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  90. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  91. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  92. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  93. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  94. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  95. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  96. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  97. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  98. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  99. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  100. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  101. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  102. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  103. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  104. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  105. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  106. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  107. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  108. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  109. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  110. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  111. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  112. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  113. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  114. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  115. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  116. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  117. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  118. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  119. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  120. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  121. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  122. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  123. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  124. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  125. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  126. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  127. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  128. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  129. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  130. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  131. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  132. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  133. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  134. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  135. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  136. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  137. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  138. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  139. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  140. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  141. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  142. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  143. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  144. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  145. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  146. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  147. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  148. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  149. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  150. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  151. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  152. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  153. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  154. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  155. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  156. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  157. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  158. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  159. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  160. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  161. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  162. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  163. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  164. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  165. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  166. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  167. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  168. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  169. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  170. {sarj_python_lint-1.13.0 → sarj_python_lint-1.14.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.13.0
3
+ Version: 1.14.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.13.0"
3
+ version = "1.14.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" }]
@@ -98,6 +98,7 @@ from sarj_python_lint.rules.no_whole_request_response_payload_in_log import (
98
98
  from sarj_python_lint.rules.opaque_parametrize_case_needs_id import OpaqueParametrizeCaseNeedsId
99
99
  from sarj_python_lint.rules.over_mocked_test import OverMockedTest
100
100
  from sarj_python_lint.rules.phase_label_comment import TestPhaseLabelComment
101
+ from sarj_python_lint.rules.prefer_autospec_for_callable_mock import PreferAutospecForCallableMock
101
102
  from sarj_python_lint.rules.prefer_class_row import PreferClassRow
102
103
  from sarj_python_lint.rules.prefer_collection_comprehension import PreferCollectionComprehension
103
104
  from sarj_python_lint.rules.prefer_constant_time_secret_compare import (
@@ -248,6 +249,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
248
249
  PreferInjectedDependencyOverMonkeypatch.id: PreferInjectedDependencyOverMonkeypatch,
249
250
  PreferMonkeypatchForProcessStateInTest.id: PreferMonkeypatchForProcessStateInTest,
250
251
  MockWithoutSpec.id: MockWithoutSpec,
252
+ PreferAutospecForCallableMock.id: PreferAutospecForCallableMock,
251
253
  OpaqueParametrizeCaseNeedsId.id: OpaqueParametrizeCaseNeedsId,
252
254
  PytestFixtureReturnsBareTuple.id: PytestFixtureReturnsBareTuple,
253
255
  StoreGetDelegatesToBulkRead.id: StoreGetDelegatesToBulkRead,
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ from collections import Counter
4
5
  from dataclasses import dataclass
5
6
  from pathlib import PurePosixPath
6
7
  from typing import TYPE_CHECKING, ClassVar, final, override
@@ -48,7 +49,7 @@ _FALLBACK_VALUES = 2
48
49
  @dataclass(frozen=True, slots=True)
49
50
  class _ScopeUsage:
50
51
  parents: dict[ast.AST, ast.AST]
51
- called: set[str]
52
+ calls: dict[str, list[ast.Call]]
52
53
 
53
54
 
54
55
  @final
@@ -66,6 +67,7 @@ class NoNullableDependencyFallback(Rule):
66
67
  limitations=(
67
68
  "Warns for None-defaulted function parameters with a fallback used as a callable or a proven application-settings fallback; genuine absent state is allowed.",
68
69
  "Constructors remain owned by SARJ095 and SARJ468. Decorated functions, tests, generated code, nested scope captures, non-callable object fallbacks, and interprocedural forwarding are excluded.",
70
+ "Callable use through a local assignment, annotation or None guard requires at most one body binding for that name and a call after the binding statement completes. Rebinding, deletion, imports, definitions, pattern or exception captures, global/nonlocal declarations and nested-scope bindings conservatively exclude that inference; direct inline calls and proven settings fallbacks remain independent.",
69
71
  "No autofix: removing explicit None acceptance changes the callable contract, and moving a fallback can change initialization timing or error handling.",
70
72
  ),
71
73
  examples=(
@@ -163,6 +165,8 @@ def _hidden_dependencies(
163
165
  guard = _guarded_fallback(statement, candidates)
164
166
  bindings = LocalBindingCollector()
165
167
  bindings.visit(statement)
168
+ bindings.names.update(name for node in _scope_nodes(statement) for name in _binding_names(node))
169
+ bindings.names.update(node.target.id for node in ast.walk(statement) if isinstance(node, ast.NamedExpr))
166
170
  # Branch/loop bindings are conservatively local; do not infer data flow.
167
171
  if not isinstance(statement, (ast.Assign, ast.AnnAssign)) and guard is None:
168
172
  candidates.difference_update(bindings.names)
@@ -177,7 +181,7 @@ def _hidden_dependencies(
177
181
  )
178
182
  if guard is not None:
179
183
  name, value = guard
180
- if name in usage.called or _is_settings_fallback(value, resolver, shadowed):
184
+ if _called_after(name, statement, usage) or _is_settings_fallback(value, resolver, shadowed):
181
185
  hidden.add(name)
182
186
  candidates.difference_update(bindings.names)
183
187
  return hidden
@@ -186,8 +190,41 @@ def _hidden_dependencies(
186
190
  def _scope_usage(function: ast.FunctionDef | ast.AsyncFunctionDef) -> _ScopeUsage:
187
191
  nodes = [node for statement in function.body for node in _scope_nodes(statement)]
188
192
  parents = {child: node for node in nodes for child in ast.iter_child_nodes(node)}
189
- called = {node.func.id for node in nodes if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)}
190
- return _ScopeUsage(parents=parents, called=called)
193
+ bindings = Counter(
194
+ name for statement in function.body for node in ast.walk(statement) for name in _binding_names(node)
195
+ )
196
+ calls: dict[str, list[ast.Call]] = {}
197
+ for node in nodes:
198
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and bindings[node.func.id] <= 1:
199
+ calls.setdefault(node.func.id, []).append(node)
200
+ return _ScopeUsage(parents=parents, calls=calls)
201
+
202
+
203
+ def _called_after(name: str, binding: ast.stmt, usage: _ScopeUsage) -> bool:
204
+ end = (binding.end_lineno or binding.lineno, binding.end_col_offset or binding.col_offset)
205
+ return any((call.lineno, call.col_offset) > end for call in usage.calls.get(name, ()))
206
+
207
+
208
+ def _binding_names(node: ast.AST) -> Iterator[str]:
209
+ match node:
210
+ case (
211
+ ast.Name(id=name, ctx=(ast.Store() | ast.Del()))
212
+ | ast.arg(arg=name)
213
+ | ast.FunctionDef(name=name)
214
+ | ast.AsyncFunctionDef(name=name)
215
+ | ast.ClassDef(name=name)
216
+ | ast.ExceptHandler(name=str() as name)
217
+ | ast.MatchAs(name=str() as name)
218
+ | ast.MatchStar(name=str() as name)
219
+ | ast.MatchMapping(rest=str() as name)
220
+ ):
221
+ yield name
222
+ case ast.alias(name=imported, asname=alias):
223
+ yield alias or imported.partition(".")[0]
224
+ case ast.Global(names=names) | ast.Nonlocal(names=names):
225
+ yield from names
226
+ case _:
227
+ return
191
228
 
192
229
 
193
230
  def _expression_dependencies(
@@ -270,7 +307,10 @@ def _used_as_dependency(node: ast.AST, usage: _ScopeUsage) -> bool:
270
307
  parent = usage.parents.get(node)
271
308
  if isinstance(parent, ast.Call) and parent.func is node:
272
309
  return True
273
- if isinstance(parent, ast.Assign) and parent.value is node and len(parent.targets) == 1:
274
- target = parent.targets[0]
275
- return isinstance(target, ast.Name) and target.id in usage.called
276
- return False
310
+ match parent:
311
+ case (
312
+ ast.Assign(targets=[ast.Name(id=name)], value=value) | ast.AnnAssign(target=ast.Name(id=name), value=value)
313
+ ) if value is node:
314
+ return _called_after(name, parent, usage)
315
+ case _:
316
+ return False
@@ -0,0 +1,326 @@
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
+ from sarj_python_lint.rules._ast_index import walk
21
+ from sarj_python_lint.rules._paths import is_test_path
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Iterable, Mapping
26
+
27
+ from sarj_python_lint._file_context import PythonFileContext
28
+
29
+
30
+ _MOCK_SOURCES = frozenset({"unittest.mock"})
31
+ _CONSTRUCTORS = frozenset({"Mock", "MagicMock", "AsyncMock"})
32
+ _CONSTRUCTOR_OPTIONS = frozenset({"spec", "spec_set", "return_value", "name", "unsafe", "autospec"})
33
+ _SAFE_ATTRIBUTES = frozenset(
34
+ {
35
+ "assert_called",
36
+ "assert_called_once",
37
+ "assert_called_with",
38
+ "assert_called_once_with",
39
+ "assert_any_call",
40
+ "assert_has_calls",
41
+ "assert_not_called",
42
+ "assert_awaited",
43
+ "assert_awaited_once",
44
+ "assert_awaited_with",
45
+ "assert_awaited_once_with",
46
+ "assert_any_await",
47
+ "assert_has_awaits",
48
+ "assert_not_awaited",
49
+ "called",
50
+ "call_count",
51
+ "call_args",
52
+ "call_args_list",
53
+ "mock_calls",
54
+ "await_count",
55
+ "await_args",
56
+ "await_args_list",
57
+ "return_value",
58
+ }
59
+ )
60
+ _REFLECTION = frozenset({"globals", "locals", "vars", "eval", "exec", "setattr", "delattr"})
61
+ _REFLECTION_TARGETS = frozenset(f"builtins.{name}" for name in _REFLECTION)
62
+ _SCOPES = (
63
+ ast.Module,
64
+ ast.FunctionDef,
65
+ ast.AsyncFunctionDef,
66
+ ast.ClassDef,
67
+ ast.Lambda,
68
+ ast.ListComp,
69
+ ast.SetComp,
70
+ ast.DictComp,
71
+ ast.GeneratorExp,
72
+ )
73
+ _BAD = (
74
+ "from unittest.mock import Mock\n"
75
+ "def send(*, recipient: str) -> None:\n pass\n"
76
+ "def test_send():\n double = Mock(spec=send)\n double(recipient='sample')\n"
77
+ )
78
+ _GOOD = _BAD.replace("Mock", "create_autospec").replace("spec=send", "send, spec_set=True")
79
+
80
+
81
+ @final
82
+ class PreferAutospecForCallableMock(Rule):
83
+ id = "prefer-autospec-for-callable-mock"
84
+ code = "SARJ474"
85
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
86
+ default_level=Severity.WARNING,
87
+ summary="Prefer autospec for specced function mocks called with explicit keywords.",
88
+ rationale=(
89
+ "Mock, MagicMock and AsyncMock with spec or spec_set allow incompatible arguments during invocation. "
90
+ "Tests that assert only results or call counts can therefore accept a misspelled keyword or stale call shape."
91
+ ),
92
+ remediation=(
93
+ "Use create_autospec(function, spec_set=True), preserving configured results. "
94
+ "An async function produces an awaitable mock whose signature is checked when awaited. "
95
+ "Retain a reasoned local exception for a deliberately permissive double."
96
+ ),
97
+ category=RuleCategory.TESTING,
98
+ autofix=AutofixPolicy.NONE,
99
+ limitations=(
100
+ "Only authored test files, proven unittest.mock constructors, earlier undecorated non-generic module-local function specs without **kwargs, and unconditional simple mock assignments are analyzed.",
101
+ "Requires a stable binding and at least one direct call with explicit keywords in the same scope. Escapes, aliases, rebinding, dynamic argument unpacking, wildcard imports and reflective namespace mutation are excluded.",
102
+ "Imported functions, class or descriptor specs, wrapping, side effects and uncertain configuration are excluded. AsyncMock requires an async function spec so autospec preserves awaiting. Import and function shadow checks, including exception/pattern captures and importer-root walrus/global writes, are conservative across the file. Proven builtins reflection aliases are excluded.",
103
+ "Exact call-shape assertions such as assert_called_once_with can already validate a spec's signature; this advisory warning asks for validation during invocation. Autospec does not enforce argument value types.",
104
+ "Passing autospec=True to a Mock constructor merely configures an attribute; it does not activate autospeccing. Use create_autospec instead.",
105
+ "The initial bounded private-platform survey found zero direct matches within this scope; this is a preventive contract warning, not evidence of a current platform incident. No autofix or blocking promotion is proposed.",
106
+ ),
107
+ examples=(
108
+ RuleExample(
109
+ example_id="specced-function-direct-call",
110
+ title="A function spec leaves invocation permissive",
111
+ outcome=ExampleOutcome.MATCH,
112
+ files=(ExampleFile.python("tests/test_delivery.py", _BAD),),
113
+ focus_path=PurePosixPath("tests/test_delivery.py"),
114
+ expected_count=1,
115
+ public=True,
116
+ ),
117
+ RuleExample(
118
+ example_id="autospecced-function-direct-call",
119
+ title="Autospec checks the function call shape",
120
+ outcome=ExampleOutcome.NO_MATCH,
121
+ files=(ExampleFile.python("tests/test_delivery.py", _GOOD),),
122
+ focus_path=PurePosixPath("tests/test_delivery.py"),
123
+ expected_count=0,
124
+ public=True,
125
+ ),
126
+ ),
127
+ )
128
+ description = documentation.summary
129
+
130
+ @override
131
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
132
+ if not is_test_path(context.path) or context.generated or "Mock" not in context.source:
133
+ return []
134
+ tree = context.tree
135
+ if tree is None or _ambiguous_namespace(context):
136
+ return []
137
+ specs = _stable_specs(tree)
138
+ if not specs:
139
+ return []
140
+ findings: list[Diagnostic] = []
141
+ scopes = [tree, *context.nodes(ast.FunctionDef, ast.AsyncFunctionDef)]
142
+ for scope in scopes:
143
+ scope_nodes = tuple(walk(scope))
144
+ bindings = _binding_counts(scope_nodes)
145
+ for statement in scope.body:
146
+ candidate = _mock_binding(statement, specs, context)
147
+ if candidate is None:
148
+ continue
149
+ name, call = candidate
150
+ if bindings[name] != 1 or not _closed_fixed_calls(name, call, scope, scope_nodes, context.parents):
151
+ continue
152
+ if is_suppressed(context.source_lines, call.lineno, self.code):
153
+ continue
154
+ findings.append(
155
+ Diagnostic(
156
+ path=context.path,
157
+ line=call.lineno,
158
+ col=call.col_offset + 1,
159
+ code=self.code,
160
+ message="A function spec does not check arguments during invocation; use create_autospec(function, spec_set=True) for this callable mock.",
161
+ severity=Severity.WARNING,
162
+ )
163
+ )
164
+ return findings
165
+
166
+
167
+ def _stable_specs(tree: ast.Module) -> dict[str, ast.FunctionDef | ast.AsyncFunctionDef]:
168
+ bindings = _binding_counts(walk(tree))
169
+ return {
170
+ node.name: node
171
+ for node in tree.body
172
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
173
+ and not node.decorator_list
174
+ and not node.type_params
175
+ and node.args.kwarg is None
176
+ and bindings[node.name] == 1
177
+ }
178
+
179
+
180
+ def _binding_counts(nodes: Iterable[ast.AST]) -> Counter[str]:
181
+ counts: Counter[str] = Counter()
182
+ for node in nodes:
183
+ match node:
184
+ case (
185
+ ast.Name(id=name, ctx=(ast.Store() | ast.Del()))
186
+ | ast.arg(arg=name)
187
+ | ast.FunctionDef(name=name)
188
+ | ast.AsyncFunctionDef(name=name)
189
+ | ast.ClassDef(name=name)
190
+ | ast.ExceptHandler(name=str(name))
191
+ | ast.MatchAs(name=str(name))
192
+ | ast.MatchStar(name=str(name))
193
+ | ast.MatchMapping(rest=str(name))
194
+ ):
195
+ counts[name] += 1
196
+ case ast.alias(name=name, asname=alias):
197
+ counts[alias or name.partition(".")[0]] += 1
198
+ case ast.Global(names=names) | ast.Nonlocal(names=names):
199
+ counts.update(names)
200
+ case _:
201
+ pass
202
+ return counts
203
+
204
+
205
+ def _mock_binding(
206
+ statement: ast.stmt, specs: Mapping[str, ast.FunctionDef | ast.AsyncFunctionDef], context: PythonFileContext
207
+ ) -> tuple[str, ast.Call] | None:
208
+ match statement:
209
+ case (
210
+ ast.Assign(targets=[ast.Name(id=name)], value=ast.Call() as call)
211
+ | ast.AnnAssign(target=ast.Name(id=name), value=ast.Call() as call)
212
+ ):
213
+ pass
214
+ case _:
215
+ return None
216
+ constructor = context.imports.resolved_symbol(call.func, sources=_MOCK_SOURCES)
217
+ if call.args or constructor not in _CONSTRUCTORS:
218
+ return None
219
+ if any(keyword.arg not in _CONSTRUCTOR_OPTIONS for keyword in call.keywords):
220
+ return None
221
+ contracts = [keyword.value for keyword in call.keywords if keyword.arg in {"spec", "spec_set"}]
222
+ if len(contracts) != 1 or not isinstance(contracts[0], ast.Name) or contracts[0].id not in specs:
223
+ return None
224
+ spec = specs[contracts[0].id]
225
+ if spec.lineno >= call.lineno or (constructor == "AsyncMock" and not isinstance(spec, ast.AsyncFunctionDef)):
226
+ return None
227
+ return name, call
228
+
229
+
230
+ def _closed_fixed_calls(
231
+ name: str,
232
+ constructor: ast.Call,
233
+ scope: ast.Module | ast.FunctionDef | ast.AsyncFunctionDef,
234
+ nodes: tuple[ast.AST, ...],
235
+ parents: Mapping[ast.AST, ast.AST],
236
+ ) -> bool:
237
+ called = False
238
+ for node in nodes:
239
+ if not isinstance(node, ast.Name) or node.id != name or not isinstance(node.ctx, ast.Load):
240
+ continue
241
+ if node.lineno <= constructor.lineno or _scope_of(node, parents) is not scope:
242
+ return False
243
+ parent = parents[node]
244
+ if isinstance(parent, ast.Call) and parent.func is node:
245
+ if not _fixed_arguments(parent):
246
+ return False
247
+ called = called or bool(parent.keywords)
248
+ elif _safe_mock_attribute(parent):
249
+ continue
250
+ else:
251
+ return False
252
+ return called
253
+
254
+
255
+ def _fixed_arguments(call: ast.Call) -> bool:
256
+ return not any(isinstance(argument, ast.Starred) for argument in call.args) and all(
257
+ keyword.arg is not None for keyword in call.keywords
258
+ )
259
+
260
+
261
+ def _safe_mock_attribute(node: ast.AST) -> bool:
262
+ if not isinstance(node, ast.Attribute) or node.attr not in _SAFE_ATTRIBUTES:
263
+ return False
264
+ return isinstance(node.ctx, ast.Load) or (node.attr == "return_value" and isinstance(node.ctx, ast.Store))
265
+
266
+
267
+ def _scope_of(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> ast.AST:
268
+ while node in parents:
269
+ node = parents[node]
270
+ if isinstance(node, _SCOPES):
271
+ return node
272
+ return node
273
+
274
+
275
+ def _ambiguous_namespace(context: PythonFileContext) -> bool:
276
+ if _ambiguous_constructor_bindings(context):
277
+ return True
278
+ for node in context.nodes(ast.ImportFrom, ast.Call, ast.Attribute):
279
+ if isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names):
280
+ return True
281
+ if isinstance(node, ast.Call):
282
+ if isinstance(node.func, ast.Name) and node.func.id in _REFLECTION:
283
+ return True
284
+ if context.imports.resolved_qualified_name(node.func) in _REFLECTION_TARGETS:
285
+ return True
286
+ if (
287
+ isinstance(node, ast.Attribute)
288
+ and isinstance(node.ctx, (ast.Store, ast.Del))
289
+ and context.imports.resolved_symbol(node, sources=_MOCK_SOURCES) in _CONSTRUCTORS
290
+ ):
291
+ return True
292
+ return False
293
+
294
+
295
+ def _ambiguous_constructor_bindings(context: PythonFileContext) -> bool:
296
+ constructor_roots = _constructor_roots(context)
297
+ for node in context.nodes(
298
+ ast.ExceptHandler, ast.MatchAs, ast.MatchStar, ast.MatchMapping, ast.NamedExpr, ast.Global
299
+ ):
300
+ match node:
301
+ case (
302
+ ast.ExceptHandler(name=name)
303
+ | ast.MatchAs(name=name)
304
+ | ast.MatchStar(name=name)
305
+ | ast.MatchMapping(rest=name)
306
+ | ast.NamedExpr(target=ast.Name(id=name))
307
+ ):
308
+ if name in constructor_roots:
309
+ return True
310
+ case ast.Global(names=names):
311
+ if constructor_roots.intersection(names):
312
+ return True
313
+ return False
314
+
315
+
316
+ def _constructor_roots(context: PythonFileContext) -> set[str]:
317
+ constructor_roots: set[str] = set()
318
+ for call in context.nodes(ast.Call):
319
+ if context.imports.resolved_symbol(call.func, sources=_MOCK_SOURCES) not in _CONSTRUCTORS:
320
+ continue
321
+ root = call.func
322
+ while isinstance(root, ast.Attribute):
323
+ root = root.value
324
+ if isinstance(root, ast.Name):
325
+ constructor_roots.add(root.id)
326
+ return constructor_roots