sarj-python-lint 1.97.0__tar.gz → 1.99.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 (313) hide show
  1. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/__main__.py +1 -0
  4. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_rule_descriptors.py +9 -0
  5. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  6. sarj_python_lint-1.99.0/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +515 -0
  7. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/.gitignore +0 -0
  8. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/LICENSE +0 -0
  9. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/README.md +0 -0
  10. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/__init__.py +0 -0
  11. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix.py +0 -0
  13. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  14. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  15. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  16. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  17. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  18. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  19. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  20. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  21. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  22. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  23. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  24. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  25. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  26. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  27. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_file_context.py +0 -0
  28. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_filesystem.py +0 -0
  29. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_fixes.py +0 -0
  30. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_parallel.py +0 -0
  31. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
  32. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_python_target.py +0 -0
  33. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  34. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_secret_names.py +0 -0
  35. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_source.py +0 -0
  36. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_version.py +0 -0
  37. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/application_logging.py +0 -0
  38. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/autofix.py +0 -0
  39. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/benchmark.py +0 -0
  40. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/cast_access.py +0 -0
  41. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  42. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/json_boundary.py +0 -0
  43. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/py.typed +0 -0
  44. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/ratchet.py +0 -0
  45. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rule_base.py +0 -0
  46. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rule_metadata.py +0 -0
  47. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  48. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  49. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  50. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  51. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  52. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  53. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  54. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  55. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  56. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  57. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  58. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  59. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  60. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  61. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  62. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  63. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  64. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  65. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  66. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  67. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  68. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  69. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  70. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  71. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  72. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  73. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  74. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  75. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  76. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  77. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  78. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  79. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  80. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  81. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  82. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
  83. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  84. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  85. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  86. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  87. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  88. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  89. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  90. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  91. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
  92. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  93. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  94. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  95. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  96. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  97. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  98. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  99. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  100. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  101. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  102. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  103. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  104. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  105. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  106. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  107. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  108. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  109. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  110. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  111. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  112. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  113. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  114. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  115. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  116. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  117. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  118. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  119. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  120. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  121. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  122. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  123. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  124. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  125. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  126. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  127. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  128. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  129. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  130. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  131. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  132. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  133. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  134. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  135. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  136. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  137. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  138. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  139. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  140. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  141. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  142. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  143. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  144. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  145. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  146. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  147. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  148. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  149. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  150. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  151. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  152. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  153. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  154. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  155. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  156. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  157. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  158. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  159. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  160. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  161. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  162. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  163. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  164. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
  165. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  166. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  167. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  168. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  169. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  170. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  171. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  172. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  173. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  174. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  175. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  176. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  177. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  178. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  179. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
  180. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  181. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  182. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  183. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  184. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  185. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  186. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  187. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  188. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  189. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  190. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
  191. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  192. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  193. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  194. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  195. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  196. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  197. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  198. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  199. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  200. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  201. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  202. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  203. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  204. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  205. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  206. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  207. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
  208. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  209. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  210. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  211. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  212. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  213. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  214. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  215. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  216. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  217. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  218. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  219. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  220. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  221. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  222. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  223. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  224. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  225. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  226. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  227. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  228. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  229. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  230. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  231. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  232. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  233. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  234. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  235. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  236. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  237. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  238. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  239. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  240. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  241. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  242. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  243. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  244. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  245. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  246. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  247. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  248. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  249. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  250. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  251. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  252. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  253. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  254. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
  255. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  256. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  257. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  258. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  259. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  260. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  261. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  262. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  263. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  264. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  265. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  266. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  267. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  268. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  269. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  270. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  271. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  272. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  273. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  274. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  275. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  276. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  277. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  278. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  279. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  280. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  281. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  282. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
  283. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  284. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  285. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  286. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  287. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  288. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  289. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  290. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  291. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  292. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  293. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  294. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  295. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  296. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  297. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  298. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  299. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  300. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  301. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  302. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  303. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  304. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  305. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  306. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  307. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  308. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  309. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  310. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  311. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  312. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  313. {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.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.97.0
3
+ Version: 1.99.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.97.0"
3
+ version = "1.99.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" }]
@@ -253,6 +253,7 @@ def analyze(
253
253
 
254
254
  _DIAGNOSTIC_PRECEDENCE = MappingProxyType(
255
255
  {
256
+ "SARJ556": frozenset({"SARJ421"}),
256
257
  "SARJ020": frozenset({"SARJ437"}),
257
258
  "SARJ054": frozenset({"SARJ419"}),
258
259
  "SARJ448": frozenset({"SARJ419"}),
@@ -8,6 +8,15 @@ from sarj_python_lint.rule_metadata import RuleDescriptor
8
8
 
9
9
  SCHEMA_VERSION = 1
10
10
  DESCRIPTORS: tuple[RuleDescriptor, ...] = (
11
+ RuleDescriptor(
12
+ rule_id=RuleId("service-singleton-delegates-to-bulk"),
13
+ module="sarj_python_lint.rules.service_singleton_delegates_to_bulk",
14
+ class_name="ServiceSingletonDelegatesToBulk",
15
+ code="SARJ556",
16
+ description="Service singleton CRUD operations must delegate to a bulk primitive.",
17
+ aliases=(),
18
+ enabled_by_default=True,
19
+ ),
11
20
  RuleDescriptor(
12
21
  rule_id=RuleId("no-async-client-in-constructor"),
13
22
  module="sarj_python_lint.rules.no_async_client_in_constructor",
@@ -272,6 +272,7 @@ from sarj_python_lint.rules.pytest_fixture_returns_closed_resource import Pytest
272
272
  from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
273
273
  from sarj_python_lint.rules.repeated_unimplemented_test_contract import RepeatedUnimplementedTestContract
274
274
  from sarj_python_lint.rules.require_exp_on_jwt_decode import RequireExpOnJwtDecode
275
+ from sarj_python_lint.rules.service_singleton_delegates_to_bulk import ServiceSingletonDelegatesToBulk
275
276
  from sarj_python_lint.rules.store_field_update_reuses_upsert import StoreFieldUpdateReusesUpsert
276
277
  from sarj_python_lint.rules.store_insert_requires_explicit_conflict_policy import (
277
278
  StoreInsertRequiresExplicitConflictPolicy,
@@ -282,6 +283,7 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
282
283
 
283
284
  _AUTHORING_REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
284
285
  {
286
+ ServiceSingletonDelegatesToBulk.id: ServiceSingletonDelegatesToBulk,
285
287
  NoAsyncClientInConstructor.id: NoAsyncClientInConstructor,
286
288
  NoDiscardedTaggedOutcome.id: NoDiscardedTaggedOutcome,
287
289
  StoreInsertRequiresExplicitConflictPolicy.id: StoreInsertRequiresExplicitConflictPolicy,
@@ -0,0 +1,515 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
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
+ )
19
+ from sarj_python_lint.rules._annotation_semantics import scope_bound_names
20
+ from sarj_python_lint.rules._imports import TYPING_SOURCES
21
+ from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
22
+ from sarj_python_lint.rules._store_writes import methods, own_nodes
23
+ from sarj_python_lint.rules.require_port_for_service import is_data_type
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from sarj_python_lint._file_context import PythonFileContext
28
+ from sarj_python_lint.rules._imports import ImportIndex
29
+
30
+
31
+ type _Method = ast.FunctionDef | ast.AsyncFunctionDef
32
+ _OPERATIONS = frozenset({"get", "create", "insert", "upsert", "update", "delete"})
33
+ _ROLES = ("Service", "Store", "Repository")
34
+ _NEUTRAL_DECORATORS = frozenset(
35
+ {"typing.override", "typing_extensions.override", "typing.final", "typing_extensions.final"}
36
+ )
37
+ _ABSTRACT_DECORATORS = frozenset({"abc.abstractmethod", "typing.overload", "typing_extensions.overload"})
38
+ _NEUTRAL_BASES = frozenset({"abc.ABC", "typing.Generic", "typing_extensions.Generic"})
39
+ _MAX_STATES = 64
40
+ _UNKNOWN = "?"
41
+
42
+
43
+ @final
44
+ class ServiceSingletonDelegatesToBulk(Rule):
45
+ id = "service-singleton-delegates-to-bulk"
46
+ code = "SARJ556"
47
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
48
+ default_level=Severity.ERROR,
49
+ summary="Service singleton CRUD operations must delegate to a bulk primitive.",
50
+ rationale=(
51
+ "A singleton-only API forces callers into serial operations. Separate singleton and bulk implementations "
52
+ "duplicate filtering, validation and mutation behavior; making bulk loop through singleton preserves that "
53
+ "cost and can recurse when the singleton is later made an adapter."
54
+ ),
55
+ remediation=(
56
+ "Implement get_many, create_many, insert_many, upsert_many, update_many or delete_many as the primitive. "
57
+ "Delegate the matching singleton through a one-item collection, forwarding shared context and awaiting "
58
+ "async operations. Preserve result projection, missing-item, authorization and transaction semantics; "
59
+ "use a reasoned exact-code suppression for a boundary that cannot support batching."
60
+ ),
61
+ category=RuleCategory.ARCHITECTURE,
62
+ autofix=AutofixPolicy.NONE,
63
+ limitations=(
64
+ "Checks module-level Service, Store and Repository classes with concrete instance CRUD methods and at least one input; arbitrary domain verbs are not inferred. Existing get_by_ids bulk reads are accepted alongside get_many.",
65
+ "Tests, fakes, generated/vendor code, stubs, data classes, protocols, static/class methods, abstract/overload declarations and rewriting decorators are excluded.",
66
+ "Same-file inherited methods are resolved. Missing bulk methods are not inferred through unknown external bases. Aliased, rebound, overloaded, descriptor and decorated bulk members are deferred; dynamic dispatch and imported inheritance remain type-checker and review concerns.",
67
+ "Adapters use straight-line calls, scalar and one-item list/tuple/set aliases, and private instance helpers, with a bounded 64-state walk. Complex control flow requires an exact exception or a simple adapter helper.",
68
+ "Requires the original item and shared parameters to reach the bulk call and rejects visible reverse calls through same-class methods. It does not prove database batching, result projection, authorization, atomicity or arbitrary external side effects.",
69
+ ),
70
+ examples=(
71
+ RuleExample(
72
+ example_id="missing-bulk-api",
73
+ title="A singleton operation needs a bulk primitive",
74
+ outcome=ExampleOutcome.MATCH,
75
+ files=(
76
+ ExampleFile.python(
77
+ "app/users.py",
78
+ "class UserService:\n async def get(self, key: str):\n return await self.store.get(key)\n",
79
+ ),
80
+ ),
81
+ focus_path=PurePosixPath("app/users.py"),
82
+ expected_count=1,
83
+ public=True,
84
+ ),
85
+ RuleExample(
86
+ example_id="bulk-first-adapter",
87
+ title="Implement singleton access as a one-item bulk operation",
88
+ outcome=ExampleOutcome.NO_MATCH,
89
+ files=(
90
+ ExampleFile.python(
91
+ "app/users.py",
92
+ "class UserService:\n async def get(self, key: str):\n rows = await self.get_many([key])\n return rows[0] if rows else None\n async def get_many(self, keys: list[str]):\n return await self.store.get_many(keys)\n",
93
+ ),
94
+ ),
95
+ focus_path=PurePosixPath("app/users.py"),
96
+ expected_count=0,
97
+ public=True,
98
+ ),
99
+ RuleExample(
100
+ example_id="inverted-bulk-write",
101
+ scenario="write",
102
+ title="Bulk writes must not call back through the singleton",
103
+ outcome=ExampleOutcome.MATCH,
104
+ files=(
105
+ ExampleFile.python(
106
+ "app/users.py",
107
+ "class UserService:\n def upsert(self, user):\n return self.upsert_many([user])[0]\n def upsert_many(self, users):\n return [self.upsert(user) for user in users]\n",
108
+ ),
109
+ ),
110
+ focus_path=PurePosixPath("app/users.py"),
111
+ expected_count=1,
112
+ public=True,
113
+ ),
114
+ RuleExample(
115
+ example_id="bulk-first-write",
116
+ scenario="write",
117
+ title="Write through the bulk primitive",
118
+ outcome=ExampleOutcome.NO_MATCH,
119
+ files=(
120
+ ExampleFile.python(
121
+ "app/users.py",
122
+ "class UserService:\n def upsert(self, user):\n return self.upsert_many([user])[0]\n def upsert_many(self, users):\n return self.store.upsert_many(users)\n",
123
+ ),
124
+ ),
125
+ focus_path=PurePosixPath("app/users.py"),
126
+ expected_count=0,
127
+ public=True,
128
+ ),
129
+ ),
130
+ )
131
+ description = documentation.summary
132
+
133
+ @override
134
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
135
+ if (
136
+ context.generated
137
+ or is_test_path(context.path)
138
+ or is_test_support_path(context.path)
139
+ or context.path.suffix == ".pyi"
140
+ ):
141
+ return []
142
+ tree = context.tree
143
+ if tree is None:
144
+ return []
145
+ classes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)}
146
+ return [
147
+ diagnostic
148
+ for owner in classes.values()
149
+ if owner.name.endswith(_ROLES)
150
+ and all(
151
+ context.module_imports.resolved_qualified_name(d) in _NEUTRAL_DECORATORS for d in owner.decorator_list
152
+ )
153
+ and not is_data_type(owner)
154
+ and not any(
155
+ context.module_imports.resolves(
156
+ base.value if isinstance(base, ast.Subscript) else base, sources=TYPING_SOURCES, symbol="Protocol"
157
+ )
158
+ for base in owner.bases
159
+ )
160
+ for diagnostic in _owner_diagnostics(owner, classes, context, self.code)
161
+ ]
162
+
163
+
164
+ @dataclass(frozen=True, slots=True)
165
+ class _Surface:
166
+ methods: dict[str, _Method]
167
+ uncertain: frozenset[str]
168
+ unknown_base: bool
169
+
170
+
171
+ @dataclass(frozen=True, slots=True)
172
+ class _Call:
173
+ name: str
174
+ bindings: dict[str, str]
175
+
176
+
177
+ def _owner_diagnostics(
178
+ owner: ast.ClassDef, classes: dict[str, ast.ClassDef], context: PythonFileContext, code: str
179
+ ) -> list[Diagnostic]:
180
+ surface = _inherited_methods(owner, classes, context.module_imports, frozenset())
181
+ diagnostics: list[Diagnostic] = []
182
+ for name, singleton in methods(owner).items():
183
+ if name not in _OPERATIONS or not _concrete_instance(singleton, context.module_imports):
184
+ continue
185
+ problem = _operation_problem(singleton, surface, context.module_imports)
186
+ if problem is not None:
187
+ diagnostics.append(
188
+ Diagnostic(
189
+ path=context.path,
190
+ line=singleton.lineno,
191
+ col=singleton.col_offset + 1,
192
+ code=code,
193
+ severity=Severity.ERROR,
194
+ message=f"`{owner.name}.{name}` {problem} Implement `{name}_many` as the bulk primitive and delegate `{name}` with one item and the same context.",
195
+ )
196
+ )
197
+ return diagnostics
198
+
199
+
200
+ def _concrete_instance(method: _Method, imports: ImportIndex) -> bool:
201
+ parameters = _parameters(method)
202
+ if not parameters or method.args.vararg or method.args.kwarg:
203
+ return False
204
+ if any(imports.resolved_qualified_name(d) not in _NEUTRAL_DECORATORS for d in method.decorator_list):
205
+ return False
206
+ body = [
207
+ s
208
+ for s in method.body
209
+ if not (isinstance(s, ast.Expr) and isinstance(s.value, ast.Constant) and isinstance(s.value.value, str))
210
+ ]
211
+ return bool(body) and not all(_stub(statement) for statement in body)
212
+
213
+
214
+ def _parameters(method: _Method) -> list[ast.arg]:
215
+ positional = [*method.args.posonlyargs, *method.args.args]
216
+ if not positional or positional[0].arg != "self":
217
+ return []
218
+ return [*positional[1:], *method.args.kwonlyargs]
219
+
220
+
221
+ def _stub(statement: ast.stmt) -> bool:
222
+ if isinstance(statement, ast.Pass):
223
+ return True
224
+ if isinstance(statement, ast.Expr) and isinstance(statement.value, ast.Constant):
225
+ return statement.value.value is Ellipsis
226
+ if isinstance(statement, ast.Raise):
227
+ value = statement.exc.func if isinstance(statement.exc, ast.Call) else statement.exc
228
+ return isinstance(value, ast.Name) and value.id == "NotImplementedError"
229
+ return False
230
+
231
+
232
+ def _inherited_methods(
233
+ owner: ast.ClassDef, classes: dict[str, ast.ClassDef], imports: ImportIndex, seen: frozenset[str]
234
+ ) -> _Surface:
235
+ if owner.name in seen:
236
+ return _Surface({}, frozenset(), unknown_base=True)
237
+ result: dict[str, _Method] = {}
238
+ uncertain: set[str] = set()
239
+ unknown = False
240
+ for base in reversed(owner.bases):
241
+ target = base.value if isinstance(base, ast.Subscript) else base
242
+ if isinstance(target, ast.Name) and target.id in classes:
243
+ inherited = _inherited_methods(classes[target.id], classes, imports, seen | {owner.name})
244
+ for name in inherited.uncertain:
245
+ result.pop(name, None)
246
+ result.update(inherited.methods)
247
+ uncertain.difference_update(inherited.methods)
248
+ uncertain.update(inherited.uncertain)
249
+ unknown |= inherited.unknown_base
250
+ elif imports.resolved_qualified_name(target) not in _NEUTRAL_BASES and not (
251
+ isinstance(target, ast.Name) and target.id == "object" and imports.builtin_is_unshadowed("object")
252
+ ):
253
+ unknown = True
254
+ declared = methods(owner)
255
+ bound = _member_bindings(owner)
256
+ for name in bound:
257
+ result.pop(name, None)
258
+ result.update(declared)
259
+ uncertain.difference_update(bound)
260
+ uncertain.update(bound - declared.keys() - _literal_members(owner))
261
+ return _Surface(result, frozenset(uncertain), unknown)
262
+
263
+
264
+ def _member_bindings(owner: ast.ClassDef) -> set[str]:
265
+ bound = scope_bound_names(owner.body)
266
+ bound.update(
267
+ node.attr
268
+ for method in owner.body
269
+ if isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
270
+ for node in own_nodes(method)
271
+ if isinstance(node, ast.Attribute)
272
+ and isinstance(node.ctx, ast.Store | ast.Del)
273
+ and isinstance(node.value, ast.Name)
274
+ and node.value.id == "self"
275
+ )
276
+ return bound
277
+
278
+
279
+ def _literal_members(owner: ast.ClassDef) -> set[str]:
280
+ literals: set[str] = set()
281
+ for statement in owner.body:
282
+ literals.difference_update(scope_bound_names([statement]))
283
+ if not isinstance(statement, ast.Assign | ast.AnnAssign) or not isinstance(
284
+ statement.value, ast.Constant | ast.List | ast.Tuple | ast.Set | ast.Dict
285
+ ):
286
+ continue
287
+ targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
288
+ literals.update(target.id for target in targets if isinstance(target, ast.Name))
289
+ return literals
290
+
291
+
292
+ def _operation_problem(singleton: _Method, surface: _Surface, imports: ImportIndex) -> str | None:
293
+ bulk_name = f"{singleton.name}_many"
294
+ names = (bulk_name, "get_by_ids") if singleton.name == "get" else (bulk_name,)
295
+ if any(candidate in surface.uncertain for candidate in names):
296
+ return None
297
+ bulks = [surface.methods[candidate] for candidate in names if candidate in surface.methods]
298
+ if not bulks:
299
+ return None if surface.unknown_base else "has no declared bulk operation."
300
+ if any(not _supported_bulk(bulk, imports) for bulk in bulks):
301
+ return None
302
+ if any(_reaches(bulk.name, singleton.name, surface.methods) for bulk in bulks):
303
+ return "has a bulk operation that calls back through the singleton."
304
+ problems = [_adapter_problem(singleton, bulk, surface.methods, imports) for bulk in bulks]
305
+ return None if None in problems else problems[0]
306
+
307
+
308
+ def _supported_bulk(bulk: _Method, imports: ImportIndex) -> bool:
309
+ return (
310
+ not bulk.args.vararg
311
+ and not bulk.args.kwarg
312
+ and all(
313
+ imports.resolved_qualified_name(d) in _NEUTRAL_DECORATORS | _ABSTRACT_DECORATORS
314
+ for d in bulk.decorator_list
315
+ )
316
+ )
317
+
318
+
319
+ def _adapter_problem(
320
+ singleton: _Method, bulk: _Method, available: dict[str, _Method], imports: ImportIndex
321
+ ) -> str | None:
322
+ if not _parameters(bulk):
323
+ return "has no instance bulk operation accepting items."
324
+ if not _concrete_instance(bulk, imports) and not any(
325
+ imports.resolved_qualified_name(d) == "abc.abstractmethod" for d in bulk.decorator_list
326
+ ):
327
+ return "has only an unimplemented bulk stub."
328
+ bindings = {p.arg: f"input:{p.arg}" for p in _parameters(singleton)}
329
+ if not _delegates(singleton, bulk, available, bindings):
330
+ return "does not delegate its original item and shared context to the bulk operation."
331
+ return None
332
+
333
+
334
+ def _reaches(start: str, target: str, available: dict[str, _Method]) -> bool:
335
+ pending = [start]
336
+ seen: set[str] = set()
337
+ while pending:
338
+ name = pending.pop()
339
+ if name in seen:
340
+ continue
341
+ seen.add(name)
342
+ for node in own_nodes(available[name]):
343
+ if not isinstance(node, ast.Call):
344
+ continue
345
+ called = _self_call(node)
346
+ if called is None:
347
+ continue
348
+ if called == target:
349
+ return True
350
+ if called in available:
351
+ pending.append(called)
352
+ return False
353
+
354
+
355
+ def _self_call(call: ast.Call) -> str | None:
356
+ if isinstance(call.func, ast.Attribute) and isinstance(call.func.value, ast.Name) and call.func.value.id == "self":
357
+ return call.func.attr
358
+ return None
359
+
360
+
361
+ def _symbol(value: ast.expr, symbols: dict[str, str]) -> str:
362
+ match value:
363
+ case ast.Name(id=name):
364
+ return symbols.get(name, _UNKNOWN)
365
+ case ast.List() | ast.Tuple() | ast.Set():
366
+ items = _collection_symbols(value, symbols)
367
+ if items is None or len(items) != 1:
368
+ return _UNKNOWN
369
+ item = items[0]
370
+ return f"one:{item}" if item != _UNKNOWN else _UNKNOWN
371
+ case _:
372
+ return _UNKNOWN
373
+
374
+
375
+ def _collection_symbols(value: ast.List | ast.Tuple | ast.Set, symbols: dict[str, str]) -> list[str] | None:
376
+ items: list[str] = []
377
+ for element in value.elts:
378
+ if not isinstance(element, ast.Starred):
379
+ items.append(_symbol(element, symbols))
380
+ continue
381
+ expanded = element.value
382
+ if isinstance(expanded, ast.List | ast.Tuple | ast.Set) and not expanded.elts:
383
+ continue
384
+ symbol = _symbol(expanded, symbols)
385
+ if not symbol.startswith("one:"):
386
+ return None
387
+ items.append(symbol.removeprefix("one:"))
388
+ return items
389
+
390
+
391
+ def _delegates(singleton: _Method, bulk: _Method, available: dict[str, _Method], initial: dict[str, str]) -> bool:
392
+ pending = [_Call(singleton.name, initial)]
393
+ seen: set[tuple[str, tuple[tuple[str, str], ...]]] = set()
394
+ inputs = frozenset(initial.values())
395
+ while pending and len(seen) < _MAX_STATES:
396
+ candidate = pending.pop()
397
+ state = candidate.name, tuple(sorted(candidate.bindings.items()))
398
+ if state in seen:
399
+ continue
400
+ seen.add(state)
401
+ for call in _method_calls(available[candidate.name], available, candidate.bindings):
402
+ if call.name == bulk.name and _matches(call.bindings, inputs):
403
+ return True
404
+ if call.name.startswith("_"):
405
+ pending.append(call)
406
+ return False
407
+
408
+
409
+ def _matches(bindings: dict[str, str], inputs: frozenset[str]) -> bool:
410
+ wrapped = [value.removeprefix("one:") for value in bindings.values() if value.startswith("one:")]
411
+ if len(wrapped) != 1 or wrapped[0] not in inputs:
412
+ return False
413
+ for value in inputs - {wrapped[0]}:
414
+ name = value.removeprefix("input:")
415
+ if value not in bindings.values():
416
+ return False
417
+ if name in bindings and bindings[name] != value:
418
+ return False
419
+ return True
420
+
421
+
422
+ def _method_calls(method: _Method, available: dict[str, _Method], bindings: dict[str, str]) -> list[_Call]:
423
+ calls: list[_Call] = []
424
+ symbols = {**bindings, "self": "receiver:self"}
425
+ for statement in method.body:
426
+ if isinstance(statement, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
427
+ continue
428
+ if not isinstance(statement, ast.Assign | ast.AnnAssign | ast.Expr | ast.Return | ast.Raise | ast.Pass):
429
+ break
430
+ calls.extend(_bound_calls(statement, available, symbols))
431
+ _assignment(statement, symbols)
432
+ if isinstance(statement, ast.Return | ast.Raise):
433
+ break
434
+ return calls
435
+
436
+
437
+ def _bound_calls(statement: ast.stmt, available: dict[str, _Method], symbols: dict[str, str]) -> list[_Call]:
438
+ nodes = _eager_nodes(statement)
439
+ awaited = {id(n.value) for n in nodes if isinstance(n, ast.Await)}
440
+ calls: list[_Call] = []
441
+ for node in nodes:
442
+ if not isinstance(node, ast.Call):
443
+ continue
444
+ called = _self_call(node)
445
+ if called is None:
446
+ if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
447
+ _invalidate(node.func.value, symbols)
448
+ continue
449
+ if symbols.get("self") != "receiver:self":
450
+ continue
451
+ target = available.get(called)
452
+ if target is None or (isinstance(target, ast.AsyncFunctionDef) and id(node) not in awaited):
453
+ continue
454
+ bindings = _bind(node, target, symbols)
455
+ if bindings is not None:
456
+ calls.append(_Call(called, bindings))
457
+ return calls
458
+
459
+
460
+ def _eager_nodes(statement: ast.stmt) -> list[ast.AST]:
461
+ nodes: list[ast.AST] = []
462
+ pending: list[ast.AST] = [statement]
463
+ while pending:
464
+ node = pending.pop()
465
+ if isinstance(node, ast.Lambda | ast.GeneratorExp | ast.ListComp | ast.SetComp | ast.DictComp | ast.IfExp):
466
+ continue
467
+ nodes.append(node)
468
+ children = [node.values[0]] if isinstance(node, ast.BoolOp) else list(ast.iter_child_nodes(node))
469
+ pending.extend(reversed(children))
470
+ return nodes
471
+
472
+
473
+ def _bind(call: ast.Call, method: _Method, symbols: dict[str, str]) -> dict[str, str] | None:
474
+ positional = [*method.args.posonlyargs, *method.args.args][1:]
475
+ parameters = _parameters(method)
476
+ if len(call.args) > len(positional) or any(isinstance(arg, ast.Starred) for arg in call.args):
477
+ return None
478
+ result = {p.arg: _symbol(arg, symbols) for p, arg in zip(positional, call.args, strict=False)}
479
+ names = {p.arg for p in parameters}
480
+ positional_only = {p.arg for p in method.args.posonlyargs}
481
+ for keyword in call.keywords:
482
+ name = keyword.arg
483
+ if name is None or name not in names or name in result or name in positional_only:
484
+ return None
485
+ result[name] = _symbol(keyword.value, symbols)
486
+ required = positional[: -len(method.args.defaults)] if method.args.defaults else positional
487
+ required += [
488
+ p for p, default in zip(method.args.kwonlyargs, method.args.kw_defaults, strict=True) if default is None
489
+ ]
490
+ if any(p.arg not in result for p in required):
491
+ return None
492
+ return result
493
+
494
+
495
+ def _invalidate(target: ast.expr, symbols: dict[str, str]) -> None:
496
+ if isinstance(target, ast.Subscript):
497
+ target = target.value
498
+ if not isinstance(target, ast.Name):
499
+ return
500
+ value = symbols.get(target.id, _UNKNOWN)
501
+ for name, symbol in tuple(symbols.items()):
502
+ if symbol == value:
503
+ symbols[name] = _UNKNOWN
504
+
505
+
506
+ def _assignment(statement: ast.stmt, symbols: dict[str, str]) -> None:
507
+ if not isinstance(statement, ast.Assign | ast.AnnAssign) or statement.value is None:
508
+ return
509
+ value = _symbol(statement.value, symbols)
510
+ targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
511
+ for target in targets:
512
+ if isinstance(target, ast.Name):
513
+ symbols[target.id] = value
514
+ else:
515
+ _invalidate(target, symbols)