sarj-python-lint 1.101.10__tar.gz → 1.104.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 (316) hide show
  1. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_rule_descriptors.py +1 -1
  4. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_store_writes.py +390 -54
  5. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +4 -0
  6. sarj_python_lint-1.104.0/src/sarj_python_lint/rules/prefer_bulk_upsert.py +401 -0
  7. sarj_python_lint-1.104.0/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +928 -0
  8. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +80 -8
  9. sarj_python_lint-1.101.10/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -139
  10. sarj_python_lint-1.101.10/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -515
  11. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/.gitignore +0 -0
  12. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/LICENSE +0 -0
  13. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/README.md +0 -0
  14. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/__init__.py +0 -0
  15. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/__main__.py +0 -0
  16. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  17. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix.py +0 -0
  18. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  19. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  20. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  21. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  22. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  23. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  24. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  25. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  26. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  27. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  28. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  29. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  30. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  31. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  32. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_file_context.py +0 -0
  33. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_filesystem.py +0 -0
  34. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_fixes.py +0 -0
  35. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_parallel.py +0 -0
  36. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
  37. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_python_target.py +0 -0
  38. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  39. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_secret_names.py +0 -0
  40. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_source.py +0 -0
  41. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_version.py +0 -0
  42. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/application_logging.py +0 -0
  43. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/autofix.py +0 -0
  44. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/benchmark.py +0 -0
  45. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/cast_access.py +0 -0
  46. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  47. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/json_boundary.py +0 -0
  48. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/py.typed +0 -0
  49. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/ratchet.py +0 -0
  50. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rule_base.py +0 -0
  51. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rule_metadata.py +0 -0
  52. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  53. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  54. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  55. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  56. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  57. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  58. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  59. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  60. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  61. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  62. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  63. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  64. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  65. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  66. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  67. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  68. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  69. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  70. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  71. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  72. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  73. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  74. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  75. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  76. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  77. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  78. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  79. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  80. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  81. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  82. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  83. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  84. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  85. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  86. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  87. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
  88. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_postgres_ctas.py +0 -0
  89. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  90. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  91. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  92. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  93. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  94. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  95. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  96. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  97. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  98. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
  99. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  100. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  101. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  102. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  103. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  104. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  105. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  106. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  107. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  108. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  109. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  110. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  111. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  112. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  113. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  114. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  115. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  116. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  117. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  118. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  119. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  120. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  121. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  122. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  123. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  124. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  125. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  126. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  127. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  128. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  129. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  130. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  131. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  132. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  133. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  134. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  135. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  136. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  137. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  138. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  139. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  140. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  141. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  142. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  143. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  144. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  145. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  146. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  147. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  148. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  149. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  150. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  151. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  152. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  153. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  154. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  155. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  156. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  157. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  158. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  159. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  160. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  161. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  162. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  163. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  164. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  165. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  166. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  167. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  168. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  169. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  170. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
  171. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  172. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  173. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  174. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  175. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  176. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  177. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  178. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  179. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  180. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  181. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  182. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  183. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  184. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  185. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
  186. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  187. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  188. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  189. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  190. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  191. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  192. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  193. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  194. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  195. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
  196. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  197. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  198. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  199. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  200. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  201. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  202. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  203. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  204. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  205. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  206. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  207. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  208. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  209. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  210. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  211. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  212. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
  213. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  214. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  215. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  216. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  217. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  218. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  219. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  220. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  221. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  222. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  223. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  224. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  225. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  226. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  227. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  228. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  229. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  230. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  231. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  232. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  233. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  234. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  235. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  236. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  237. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  238. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  239. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  240. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  241. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  242. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  243. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  244. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  245. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  246. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  247. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  248. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  249. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  250. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  251. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  252. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  253. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  254. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  255. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  256. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  257. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  258. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
  259. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  260. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  261. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  262. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  263. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  264. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  265. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  266. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  267. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  268. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  269. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  270. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  271. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  272. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  273. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  274. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  275. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  276. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  277. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  278. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  279. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  280. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  281. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  282. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  283. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  284. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  285. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  286. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
  287. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  288. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  289. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  290. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  291. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  292. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  293. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  294. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  295. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  296. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  297. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  298. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  299. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  300. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  301. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  302. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  303. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  304. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  305. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  306. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  307. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  308. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  309. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  310. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  311. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  312. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  313. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  314. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  315. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  316. {sarj_python_lint-1.101.10 → sarj_python_lint-1.104.0}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.101.10
3
+ Version: 1.104.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.101.10"
3
+ version = "1.104.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" }]
@@ -256,7 +256,7 @@ DESCRIPTORS: tuple[RuleDescriptor, ...] = (
256
256
  module="sarj_python_lint.rules.prefer_bulk_upsert",
257
257
  class_name="PreferBulkUpsert",
258
258
  code="SARJ516",
259
- description="Use a declared compatible upsert_many instead of discarded serial upserts.",
259
+ description="Use declared compatible bulk APIs for repeated singleton reads and writes.",
260
260
  aliases=(),
261
261
  enabled_by_default=True,
262
262
  ),
@@ -1,21 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ from dataclasses import dataclass, replace
4
5
  from types import MappingProxyType
5
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, NamedTuple
6
7
 
7
8
  import sqlglot
8
9
  from sqlglot import exp
9
10
  from sqlglot.errors import SqlglotError
10
11
 
11
- from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
12
+ from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, bit_or_members, scope_bound_names
12
13
  from sarj_python_lint.rules._ast_index import walk
13
14
  from sarj_python_lint.rules._paths import is_generated
14
15
  from sarj_python_lint.rules._project_index import ProjectIndexSet
15
16
 
16
17
 
17
18
  if TYPE_CHECKING:
18
- from collections.abc import Iterator
19
+ from collections.abc import Iterable, Iterator
19
20
 
20
21
  from sarj_python_lint._file_context import PythonFileContext
21
22
  from sarj_python_lint.rules._imports import ImportIndex
@@ -23,6 +24,13 @@ if TYPE_CHECKING:
23
24
 
24
25
 
25
26
  type Function = ast.FunctionDef | ast.AsyncFunctionDef
27
+
28
+
29
+ class _MethodPair(NamedTuple):
30
+ singleton: Function
31
+ bulk: Function
32
+
33
+
26
34
  _COLLECTIONS = frozenset(
27
35
  {
28
36
  "collections.abc.Collection",
@@ -35,6 +43,26 @@ _COLLECTIONS = frozenset(
35
43
  }
36
44
  )
37
45
  _NEUTRAL_DECORATORS = frozenset({"abc.abstractmethod", "typing.override", "typing_extensions.override"})
46
+ _READ_SEQUENCES = frozenset({"collections.abc.Sequence", "typing.Sequence", "typing.List"})
47
+ _READ_MAPPINGS = frozenset({"collections.abc.Mapping", "typing.Mapping", "typing.Dict"})
48
+ _STORE_MEMBERS = frozenset(
49
+ {
50
+ "upsert",
51
+ "upsert_many",
52
+ "create",
53
+ "create_many",
54
+ "insert",
55
+ "insert_many",
56
+ "update",
57
+ "update_many",
58
+ "delete",
59
+ "delete_many",
60
+ "get",
61
+ "get_many",
62
+ "get_by_ids",
63
+ "__class__",
64
+ }
65
+ )
38
66
 
39
67
 
40
68
  def method_parameters(method: Function) -> list[ast.arg] | None:
@@ -66,11 +94,228 @@ def methods(owner: ast.ClassDef) -> dict[str, Function]:
66
94
  return {name: method for name, method in result.items() if name not in ambiguous}
67
95
 
68
96
 
97
+ def instance_member_bindings(owner: ast.ClassDef, context: PythonFileContext) -> set[str]:
98
+ bindings: set[str] = set()
99
+ for method in owner.body:
100
+ if not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef):
101
+ continue
102
+ for node in own_nodes(method):
103
+ if isinstance(node, ast.Attribute) and _writes_self_attribute(node, node.attr):
104
+ bindings.add(node.attr)
105
+ elif (member := _builtin_member_binding(node, method, context)) is not None:
106
+ bindings.add(member)
107
+ return bindings
108
+
109
+
110
+ def _builtin_member_binding(node: ast.AST, method: Function, context: PythonFileContext) -> str | None:
111
+ if not isinstance(node, ast.Call) or node.keywords:
112
+ return None
113
+ provenance = context.static_resource_provenance
114
+ if provenance.wildcard or provenance.dynamic_namespace:
115
+ return None
116
+ name = provenance.runtime_import_name(node.func)
117
+ if (
118
+ isinstance(node.func, ast.Name)
119
+ and node.func.id in {"setattr", "delattr"}
120
+ and context.imports.builtin_is_unshadowed(node.func.id)
121
+ and provenance.runtime_binding(node.func.id, node.func) is None
122
+ and provenance.builtin_namespace_untouched
123
+ ):
124
+ name = "builtins." + node.func.id
125
+ arity = {"builtins.setattr": 3, "builtins.delattr": 2}.get(name or "")
126
+ if arity is None or len(node.args) != arity:
127
+ return None
128
+ receiver, member = node.args[:2]
129
+ if not isinstance(member, ast.Constant) or not isinstance(member.value, str):
130
+ return None
131
+ positional = [*method.args.posonlyargs, *method.args.args]
132
+ if (
133
+ not positional
134
+ or positional[0].arg != "self"
135
+ or not isinstance(receiver, ast.Name)
136
+ or provenance.runtime_binding(receiver.id, receiver) is not positional[0]
137
+ ):
138
+ return None
139
+ return member.value
140
+
141
+
142
+ def decorator_name(
143
+ expression: ast.expr, declaration: Function | ast.ClassDef, owner: ast.ClassDef | ast.Module, imports: ImportIndex
144
+ ) -> str | None:
145
+ root = expression
146
+ while isinstance(root, ast.Attribute):
147
+ root = root.value
148
+ if not isinstance(root, ast.Name):
149
+ return None
150
+ for statement in owner.body:
151
+ if statement is declaration:
152
+ return imports.resolved_qualified_name(expression)
153
+ if _evaluation_writes(statement, root.id, names=isinstance(owner, ast.ClassDef)):
154
+ return None
155
+ return None
156
+
157
+
158
+ def _evaluation_writes(statement: ast.stmt, name: str, *, names: bool) -> bool:
159
+ pending: list[tuple[ast.AST, bool]] = [(statement, names)]
160
+ while pending:
161
+ node, names = pending.pop()
162
+ if _writes_decorator_root(node, name, names=names):
163
+ return True
164
+ pending.extend(_evaluation_children(node, names=names))
165
+ return False
166
+
167
+
168
+ def _writes_decorator_root(node: ast.AST, name: str, *, names: bool) -> bool:
169
+ match node:
170
+ case ast.Name(id=bound, ctx=ast.Store() | ast.Del()) if names:
171
+ return bound == name
172
+ case ast.Attribute(ctx=ast.Store() | ast.Del()):
173
+ root = node.value
174
+ while isinstance(root, ast.Attribute):
175
+ root = root.value
176
+ return isinstance(root, ast.Name) and root.id == name
177
+ case (
178
+ ast.FunctionDef()
179
+ | ast.AsyncFunctionDef()
180
+ | ast.ClassDef()
181
+ | ast.ExceptHandler()
182
+ | ast.MatchAs()
183
+ | ast.MatchStar()
184
+ ) if names:
185
+ return node.name == name
186
+ case ast.Import() | ast.ImportFrom() if names:
187
+ return any((alias.asname or alias.name.partition(".")[0]) == name for alias in node.names)
188
+ case ast.Global() | ast.Nonlocal():
189
+ return name in node.names
190
+ case ast.MatchMapping() if names:
191
+ return node.rest == name
192
+ case _:
193
+ return False
194
+
195
+
196
+ def _evaluation_children(node: ast.AST, *, names: bool) -> Iterable[tuple[ast.AST, bool]]:
197
+ match node:
198
+ case ast.FunctionDef() | ast.AsyncFunctionDef():
199
+ expressions = [*node.decorator_list, *node.args.defaults]
200
+ expressions.extend(default for default in node.args.kw_defaults if default is not None)
201
+ case ast.Lambda():
202
+ expressions = list(node.args.defaults)
203
+ expressions.extend(default for default in node.args.kw_defaults if default is not None)
204
+ case ast.ClassDef():
205
+ expressions = [*node.decorator_list, *node.bases, *(keyword.value for keyword in node.keywords)]
206
+ return [(child, names) for child in expressions] + [(child, False) for child in node.body]
207
+ case ast.Import() | ast.ImportFrom() | ast.AnnAssign(value=None):
208
+ return []
209
+ case ast.comprehension():
210
+ expressions = [node.iter, *node.ifs]
211
+ case _:
212
+ return [(child, names) for child in ast.iter_child_nodes(node)]
213
+ return [(child, names) for child in expressions]
214
+
215
+
216
+ def _decorator_imports(context: PythonFileContext, owner: ast.ClassDef) -> ImportIndex:
217
+ imports = context.module_imports
218
+ if context.tree is None:
219
+ return imports
220
+ preceding = context.tree.body[: context.tree.body.index(owner)]
221
+ return replace(
222
+ imports,
223
+ bindings=MappingProxyType(
224
+ {
225
+ name: target
226
+ for name, target in imports.bindings.items()
227
+ if not any(_evaluation_writes(statement, name, names=False) for statement in preceding)
228
+ }
229
+ ),
230
+ )
231
+
232
+
69
233
  def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Function] | None:
234
+ return compatible_collection_pair(owner, imports, "upsert", "upsert_many")
235
+
236
+
237
+ def compatible_collection_pair(
238
+ owner: ast.ClassDef, imports: ImportIndex, singleton_name: str, bulk_name: str
239
+ ) -> _MethodPair | None:
240
+ pair = _method_pair(owner, imports, singleton_name, bulk_name)
241
+ if pair is None:
242
+ return None
243
+ single, bulk = pair
244
+ one, many = method_parameters(single), method_parameters(bulk)
245
+ if one is None or many is None:
246
+ return None
247
+ item = collection_item(many[0].annotation, imports)
248
+ result = collection_item(bulk.returns, imports)
249
+ if (
250
+ item is None
251
+ or result is None
252
+ or not same_type(one[0].annotation, item)
253
+ or not same_type(single.returns, result)
254
+ ):
255
+ return None
256
+ return pair
257
+
258
+
259
+ def read_pair(owner: ast.ClassDef, imports: ImportIndex, bulk_name: str) -> _MethodPair | None:
260
+ pair = _method_pair(owner, imports, "get", bulk_name)
261
+ if pair is None:
262
+ return None
263
+ single, bulk = pair
264
+ one, many = method_parameters(single), method_parameters(bulk)
265
+ if one is None or many is None:
266
+ return None
267
+ item = collection_item(many[0].annotation, imports)
268
+ result = _read_result(bulk.returns, one[0].annotation, imports)
269
+ if (
270
+ item is None
271
+ or result is None
272
+ or not same_type(one[0].annotation, item)
273
+ or not same_type(_read_scalar(single.returns, imports), result)
274
+ ):
275
+ return None
276
+ return pair
277
+
278
+
279
+ def _read_scalar(annotation: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
280
+ annotation = AnnotationSemantics.parse(annotation)
281
+ if isinstance(annotation, ast.Subscript) and imports.resolved_qualified_name(annotation.value) in {
282
+ "typing.Optional",
283
+ "typing_extensions.Optional",
284
+ }:
285
+ return annotation.slice if not isinstance(annotation.slice, ast.Tuple) else None
286
+ if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
287
+ match bit_or_members(annotation):
288
+ case [value, ast.Constant(value=None)] | [ast.Constant(value=None), value]:
289
+ return None if isinstance(value, ast.Constant) and value.value is None else value
290
+ case _:
291
+ return None
292
+ return None if isinstance(annotation, ast.Constant) and annotation.value is None else annotation
293
+
294
+
295
+ def _read_result(annotation: ast.expr | None, key: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
296
+ annotation = AnnotationSemantics.parse(annotation)
297
+ if not isinstance(annotation, ast.Subscript):
298
+ return None
299
+ container = annotation.value
300
+ name = imports.resolved_qualified_name(container)
301
+ if isinstance(container, ast.Name) and imports.builtin_is_unshadowed(container.id):
302
+ name = "builtins." + container.id
303
+ if name in {*_READ_SEQUENCES, "builtins.list"}:
304
+ return annotation.slice if not isinstance(annotation.slice, ast.Tuple) else None
305
+ if name in {*_READ_MAPPINGS, "builtins.dict"}:
306
+ match annotation.slice:
307
+ case ast.Tuple(elts=[mapping_key, value]) if same_type(key, mapping_key):
308
+ return value
309
+ case _:
310
+ pass
311
+ return None
312
+
313
+
314
+ def _method_pair(owner: ast.ClassDef, imports: ImportIndex, singleton_name: str, bulk_name: str) -> _MethodPair | None:
70
315
  if owner.keywords:
71
316
  return None
72
317
  declared = methods(owner)
73
- single, bulk = declared.get("upsert"), declared.get("upsert_many")
318
+ single, bulk = declared.get(singleton_name), declared.get(bulk_name)
74
319
  if (
75
320
  single is None
76
321
  or bulk is None
@@ -78,7 +323,7 @@ def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Fu
78
323
  ):
79
324
  return None
80
325
  if any(
81
- imports.resolved_qualified_name(decorator) not in _NEUTRAL_DECORATORS
326
+ decorator_name(decorator, method, owner, imports) not in _NEUTRAL_DECORATORS
82
327
  for method in (single, bulk)
83
328
  for decorator in method.decorator_list
84
329
  ):
@@ -86,16 +331,7 @@ def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Fu
86
331
  one, many = method_parameters(single), method_parameters(bulk)
87
332
  if one is None or many is None or len(one) != 1 or len(many) != 1:
88
333
  return None
89
- item = collection_item(many[0].annotation, imports)
90
- result = collection_item(bulk.returns, imports)
91
- if (
92
- item is None
93
- or result is None
94
- or not same_type(one[0].annotation, item)
95
- or not same_type(single.returns, result)
96
- ):
97
- return None
98
- return single, bulk
334
+ return _MethodPair(single, bulk)
99
335
 
100
336
 
101
337
  def collection_item(annotation: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
@@ -134,15 +370,7 @@ def own_nodes(node: ast.AST) -> Iterator[ast.AST]:
134
370
  def discarded_call(loop: ast.For) -> tuple[ast.Call, bool] | None:
135
371
  if loop.orelse or len(loop.body) != 1 or not isinstance(loop.body[0], ast.Expr):
136
372
  return None
137
- if (
138
- isinstance(loop.iter, ast.List | ast.Tuple | ast.Set)
139
- and len(loop.iter.elts) <= 1
140
- and not any(isinstance(item, ast.Starred) for item in loop.iter.elts)
141
- ):
142
- return None
143
- if isinstance(loop.iter, ast.Dict) and len(loop.iter.keys) <= 1 and None not in loop.iter.keys:
144
- return None
145
- if isinstance(loop.iter, ast.Constant) and isinstance(loop.iter.value, str | bytes) and len(loop.iter.value) <= 1:
373
+ if known_single_iteration(loop.iter):
146
374
  return None
147
375
  value = loop.body[0].value
148
376
  awaited = isinstance(value, ast.Await)
@@ -150,6 +378,18 @@ def discarded_call(loop: ast.For) -> tuple[ast.Call, bool] | None:
150
378
  return (value, awaited) if isinstance(value, ast.Call) else None
151
379
 
152
380
 
381
+ def known_single_iteration(iterable: ast.expr) -> bool:
382
+ match iterable:
383
+ case ast.List(elts=items) | ast.Tuple(elts=items) | ast.Set(elts=items):
384
+ return len(items) <= 1 and not any(isinstance(item, ast.Starred) for item in items)
385
+ case ast.Dict(keys=keys):
386
+ return len(keys) <= 1 and None not in keys
387
+ case ast.Constant(value=str() | bytes() as value):
388
+ return len(value) <= 1
389
+ case _:
390
+ return False
391
+
392
+
153
393
  def depends_on_target(expression: ast.expr, target: ast.expr) -> bool:
154
394
  names = _target_names(target)
155
395
  return any(name in names for name in _free_names(expression))
@@ -187,11 +427,124 @@ def _comprehension_scopes(
187
427
  yield from ((value, shadowed) for value in values)
188
428
 
189
429
 
430
+ @dataclass(frozen=True, slots=True)
431
+ class _ReceiverOrigin:
432
+ binding: ast.AST
433
+ attribute: str | None = None
434
+
435
+
436
+ @dataclass(frozen=True, order=True, slots=True)
437
+ class _SourcePosition:
438
+ line: int
439
+ column: int
440
+
441
+
442
+ class _ReceiverAliases:
443
+ def __init__(self, context: PythonFileContext) -> None:
444
+ self.context = context
445
+ self.provenance = context.static_resource_provenance
446
+
447
+ def origins(self, expression: ast.expr, remaining: int = 16) -> frozenset[_ReceiverOrigin] | None:
448
+ if remaining == 0:
449
+ return None
450
+ if isinstance(expression, ast.Attribute):
451
+ return self._injected_origin(expression)
452
+ if not isinstance(expression, ast.Name):
453
+ return frozenset()
454
+ bindings = self._preceding_bindings(expression)
455
+ origins: set[_ReceiverOrigin] = set()
456
+ for binding in bindings:
457
+ match binding:
458
+ case ast.arg():
459
+ origins.add(_ReceiverOrigin(binding))
460
+ case ast.Name():
461
+ assignment = self.context.parents.get(binding)
462
+ if (
463
+ isinstance(assignment, ast.Assign | ast.AnnAssign)
464
+ and assignment.value is not None
465
+ and binding
466
+ in (assignment.targets if isinstance(assignment, ast.Assign) else [assignment.target])
467
+ ):
468
+ assigned = self.origins(assignment.value, remaining - 1)
469
+ if assigned is None:
470
+ return None
471
+ origins.update(assigned)
472
+ case _:
473
+ pass
474
+ return frozenset(origins)
475
+
476
+ def replaced(self, origin: _ReceiverOrigin, nodes: Iterable[ast.AST]) -> bool:
477
+ return any(
478
+ (origins := self.origins(target)) is None or origin in origins
479
+ for node in nodes
480
+ if (target := _replaced_receiver(node, self.context.imports)) is not None
481
+ )
482
+
483
+ def _preceding_bindings(self, expression: ast.Name) -> tuple[ast.AST, ...]:
484
+ scope = self.provenance.scope(expression)
485
+ while scope is not None:
486
+ bindings = self.provenance.bindings.get(scope, {}).get(expression.id)
487
+ if bindings:
488
+ return self._reaching_bindings(bindings, expression, scope)
489
+ scope = self.provenance.scope(scope)
490
+ return ()
491
+
492
+ def _reaching_bindings(
493
+ self, bindings: tuple[ast.AST, ...], expression: ast.Name, scope: ast.AST
494
+ ) -> tuple[ast.AST, ...]:
495
+ preceding = sorted(
496
+ (binding for binding in bindings if self._precedes_read(binding, expression)), key=_source_position
497
+ )
498
+ # A definite local overwrite discards earlier possible objects; conditional
499
+ # writes retain all possible origins until another definite overwrite.
500
+ start = 0
501
+ for index, binding in enumerate(preceding):
502
+ statement = self.context.parents.get(binding)
503
+ if (
504
+ isinstance(binding, ast.arg)
505
+ or self.context.parents.get(binding) is scope
506
+ or (
507
+ isinstance(statement, ast.Assign | ast.AnnAssign | ast.Delete | ast.Import | ast.ImportFrom)
508
+ and self.context.parents.get(statement) is scope
509
+ )
510
+ ):
511
+ start = index
512
+ return tuple(preceding[start:])
513
+
514
+ def _precedes_read(self, binding: ast.AST, expression: ast.Name) -> bool:
515
+ assignment = self.context.parents.get(binding)
516
+ return _source_position(binding) < _source_position(expression) and not (
517
+ isinstance(assignment, ast.Assign | ast.AnnAssign)
518
+ and (assignment.value is None or assignment.value is expression)
519
+ )
520
+
521
+ def _injected_origin(self, expression: ast.Attribute) -> frozenset[_ReceiverOrigin]:
522
+ if not isinstance(expression.value, ast.Name) or expression.value.id != "self":
523
+ return frozenset()
524
+ binding = self.provenance.binding("self", expression)
525
+ arguments = self.context.parents.get(binding) if binding is not None else None
526
+ method = self.context.parents.get(arguments) if arguments is not None else None
527
+ owner = self.context.parents.get(method) if method is not None else None
528
+ if (
529
+ isinstance(binding, ast.arg)
530
+ and isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
531
+ and isinstance(owner, ast.ClassDef)
532
+ and [*method.args.posonlyargs, *method.args.args][:1] == [binding]
533
+ ):
534
+ return frozenset({_ReceiverOrigin(owner, expression.attr)})
535
+ return frozenset()
536
+
537
+
538
+ def _source_position(node: ast.AST) -> _SourcePosition:
539
+ return _SourcePosition(getattr(node, "lineno", -1), getattr(node, "col_offset", -1))
540
+
541
+
190
542
  class StoreOwners:
191
543
  def __init__(self, context: PythonFileContext) -> None:
192
544
  self.context = context
193
545
  self.provenance = context.static_resource_provenance
194
546
  self.project = context.session.project
547
+ self.aliases = _ReceiverAliases(context)
195
548
 
196
549
  def receiver(self, expression: ast.expr, at: ast.AST) -> tuple[ast.ClassDef, ImportIndex] | None:
197
550
  if isinstance(expression, ast.Name):
@@ -199,7 +552,7 @@ class StoreOwners:
199
552
  if (
200
553
  isinstance(binding, ast.arg)
201
554
  and binding.annotation is not None
202
- and not self._receiver_method_replaced(expression, binding)
555
+ and not self._receiver_method_replaced(binding)
203
556
  ):
204
557
  return self.annotation(binding.annotation)
205
558
  return None
@@ -214,13 +567,14 @@ class StoreOwners:
214
567
  return None
215
568
  arguments = self.context.parents.get(binding)
216
569
  method = self.context.parents.get(arguments) if arguments is not None else None
570
+ owner = self.context.parents.get(method) if method is not None else None
217
571
  if (
218
572
  not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
219
- or not isinstance(self.context.parents.get(method), ast.ClassDef)
573
+ or not isinstance(owner, ast.ClassDef)
220
574
  or not (positional := [*method.args.posonlyargs, *method.args.args])
221
575
  or positional[0] is not binding
222
576
  or any(
223
- self.context.module_imports.resolved_qualified_name(decorator) not in _NEUTRAL_DECORATORS
577
+ decorator_name(decorator, method, owner, self.context.module_imports) not in _NEUTRAL_DECORATORS
224
578
  for decorator in method.decorator_list
225
579
  )
226
580
  ):
@@ -228,16 +582,8 @@ class StoreOwners:
228
582
  annotation = self._injected_annotation(expression.attr, at)
229
583
  return self.annotation(annotation) if annotation is not None else None
230
584
 
231
- def _receiver_method_replaced(self, receiver: ast.Name, binding: ast.arg) -> bool:
232
- for node in self.context.nodes(ast.Attribute, ast.Call):
233
- target = _replaced_receiver(node, self.context.imports)
234
- if (
235
- isinstance(target, ast.Name)
236
- and target.id == receiver.id
237
- and self.provenance.binding(receiver.id, target) is binding
238
- ):
239
- return True
240
- return False
585
+ def _receiver_method_replaced(self, binding: ast.arg) -> bool:
586
+ return self.aliases.replaced(_ReceiverOrigin(binding), self.context.nodes(ast.Attribute, ast.Call))
241
587
 
242
588
  def _injected_annotation(self, attribute: str, at: ast.AST) -> ast.expr | None:
243
589
  parent = self.context.parents.get(at)
@@ -252,7 +598,9 @@ class StoreOwners:
252
598
  assignments = [
253
599
  node for method in declared_methods for node in own_nodes(method) if _writes_self_attribute(node, attribute)
254
600
  ]
255
- if len(assignments) != 1 or _injected_receiver_replaced(declared_methods, attribute, self.context.imports):
601
+ if len(assignments) != 1 or self.aliases.replaced(
602
+ _ReceiverOrigin(parent, attribute), (node for method in declared_methods for node in own_nodes(method))
603
+ ):
256
604
  return None
257
605
  value = _constructor_injected_value(constructor, assignments[0])
258
606
  return _injected_parameter(constructor, value, self.provenance) if value is not None else None
@@ -276,7 +624,7 @@ class StoreOwners:
276
624
  if isinstance(expression, ast.Name):
277
625
  declaration = _declared_class(self.context.tree, expression.id)
278
626
  if declaration is not None:
279
- return declaration, self.context.module_imports
627
+ return declaration, _decorator_imports(self.context, declaration)
280
628
  return self._imported_annotation(expression)
281
629
 
282
630
  def _imported_annotation(self, expression: ast.expr) -> tuple[ast.ClassDef, ImportIndex] | None:
@@ -301,7 +649,7 @@ class StoreOwners:
301
649
  or symbol.name in provenance.mutated
302
650
  ):
303
651
  return None
304
- return declaration, definition.module_imports
652
+ return declaration, _decorator_imports(definition, declaration)
305
653
 
306
654
 
307
655
  def _writes_self_attribute(node: ast.AST, attribute: str) -> bool:
@@ -321,17 +669,6 @@ def _injection_constructor(owner: ast.ClassDef, attribute: str) -> Function | No
321
669
  return None if attribute in scope_bound_names(runtime_statements) else methods(owner).get("__init__")
322
670
 
323
671
 
324
- def _injected_receiver_replaced(declared: list[Function], attribute: str, imports: ImportIndex) -> bool:
325
- return any(
326
- isinstance(target := _replaced_receiver(node, imports), ast.Attribute)
327
- and target.attr == attribute
328
- and isinstance(target.value, ast.Name)
329
- and target.value.id == "self"
330
- for method in declared
331
- for node in own_nodes(method)
332
- )
333
-
334
-
335
672
  def _constructor_injected_value(constructor: Function, assignment: ast.AST) -> ast.Name | None:
336
673
  for statement in constructor.body:
337
674
  match statement:
@@ -345,8 +682,7 @@ def _constructor_injected_value(constructor: Function, assignment: ast.AST) -> a
345
682
 
346
683
 
347
684
  def _replaced_receiver(node: ast.AST, imports: ImportIndex) -> ast.expr | None:
348
- members = {"upsert", "upsert_many", "__class__"}
349
- if isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Store | ast.Del) and node.attr in members:
685
+ if isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Store | ast.Del) and node.attr in _STORE_MEMBERS:
350
686
  return node.value
351
687
  if not isinstance(node, ast.Call) or node.keywords:
352
688
  return None
@@ -357,7 +693,7 @@ def _replaced_receiver(node: ast.AST, imports: ImportIndex) -> ast.expr | None:
357
693
  if required is None or len(node.args) != required:
358
694
  return None
359
695
  member = node.args[1]
360
- return node.args[0] if isinstance(member, ast.Constant) and member.value in members else None
696
+ return node.args[0] if isinstance(member, ast.Constant) and member.value in _STORE_MEMBERS else None
361
697
 
362
698
 
363
699
  def _injected_parameter(constructor: Function, value: ast.Name, provenance: ResourceProvenance) -> ast.expr | None:
@@ -160,6 +160,10 @@ def psycopg_execution_calls(
160
160
  tree = context.tree
161
161
  if tree is None:
162
162
  return []
163
+ if not any(
164
+ isinstance(call.func, ast.Attribute) and call.func.attr in _EXECUTE_METHODS for call in context.nodes(ast.Call)
165
+ ):
166
+ return []
163
167
  mutated_modules: frozenset[str] | None = frozenset()
164
168
  if (project := context.session.project) is not None:
165
169
  unit = project.unit(context.path)