sarj-python-lint 1.81.0__tar.gz → 1.82.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 (287) hide show
  1. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/pyproject.toml +1 -1
  3. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_async_lifecycle.py +124 -0
  4. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_numeric_proof.py +171 -0
  5. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_registry.py +10 -0
  6. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_executor_shutdown.py +135 -0
  7. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +221 -0
  8. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +161 -0
  9. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +289 -0
  10. sarj_python_lint-1.82.0/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +370 -0
  11. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/.gitignore +0 -0
  12. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/LICENSE +0 -0
  13. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/README.md +0 -0
  14. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__init__.py +0 -0
  15. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__main__.py +0 -0
  16. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  17. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix.py +0 -0
  18. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  19. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  20. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  21. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  22. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  23. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  24. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  25. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  26. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  27. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  28. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  29. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  30. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  31. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  32. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_file_context.py +0 -0
  33. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_filesystem.py +0 -0
  34. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_fixes.py +0 -0
  35. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_parallel.py +0 -0
  36. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_python_target.py +0 -0
  37. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  38. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_secret_names.py +0 -0
  39. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_source.py +0 -0
  40. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_version.py +0 -0
  41. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/application_logging.py +0 -0
  42. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/autofix.py +0 -0
  43. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/benchmark.py +0 -0
  44. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/cast_access.py +0 -0
  45. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  46. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/json_boundary.py +0 -0
  47. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/py.typed +0 -0
  48. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/ratchet.py +0 -0
  49. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rule_base.py +0 -0
  50. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  51. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  52. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  53. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  54. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  55. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  56. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  57. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  58. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  59. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  60. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  61. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  62. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  63. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  64. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  65. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  66. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  67. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  68. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  69. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  70. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  71. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  72. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  73. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  74. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  75. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  76. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  77. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  78. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  79. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  80. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  81. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  82. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  83. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  84. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  85. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  86. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  87. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  88. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  89. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  90. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  91. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  92. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  93. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  94. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  95. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  96. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  97. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  98. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  99. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  100. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  101. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  102. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  103. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  104. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  105. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  106. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  107. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  108. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  109. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  110. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  111. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  112. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  113. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  114. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  115. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  116. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  117. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  118. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  119. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  120. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  121. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  122. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  123. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  124. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  125. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  126. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  127. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  128. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  129. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  130. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  131. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  132. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  133. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  134. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  135. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  136. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  137. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  138. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  139. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  140. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  141. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  142. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  143. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  144. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  145. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  146. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  147. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  148. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  149. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  150. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  151. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  152. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  153. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  154. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  155. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  156. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  157. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  158. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  159. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  160. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  161. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  162. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  163. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  164. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  165. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  166. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  167. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  168. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  169. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  170. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  171. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  172. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  173. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  174. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  175. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  176. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  177. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  178. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  179. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  180. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  181. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  182. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  183. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  184. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  185. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  186. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  187. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  188. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  189. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  190. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  191. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  192. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  193. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  194. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  195. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  196. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  197. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  198. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  199. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  200. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  201. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  202. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  203. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  204. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  205. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  206. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  207. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  208. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  209. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  210. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  211. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  212. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  213. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  214. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  215. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  216. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  217. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  218. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  219. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  220. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  221. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  222. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  223. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  224. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  225. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  226. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  227. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  228. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  229. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  230. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  231. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  232. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  233. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  234. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  235. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  236. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  237. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  238. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  239. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  240. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  241. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  242. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  243. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  244. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  245. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  246. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  247. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  248. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  249. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  250. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  251. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  252. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  253. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  254. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  255. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  256. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  257. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  258. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  259. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  260. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  261. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  262. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  263. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  264. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  265. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  266. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  267. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  268. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  269. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  270. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  271. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  272. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  273. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  274. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  275. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  276. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  277. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  278. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  279. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  280. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  281. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  282. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  283. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  284. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  285. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  286. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  287. {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.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.81.0
3
+ Version: 1.82.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.81.0"
3
+ version = "1.82.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" }]
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, final
6
+
7
+ from sarj_python_lint.rules._resource_provenance import import_name_for_binding
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from sarj_python_lint._file_context import PythonFileContext
12
+
13
+
14
+ _INTROSPECTION = frozenset({"__dict__", "__setattr__", "__delattr__", "__getattribute__"})
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class _LocalAssignment:
19
+ binding: ast.Name
20
+ value: ast.expr
21
+
22
+
23
+ @final
24
+ class AsyncApiProof:
25
+ def __init__(self, context: PythonFileContext) -> None:
26
+ self.context = context
27
+ self.provenance = context.static_resource_provenance
28
+ self._namespace_safety: dict[str, bool] = {}
29
+ self._origins: dict[str, set[str]] = {}
30
+ for node in context.nodes(ast.Import, ast.ImportFrom):
31
+ if isinstance(node, ast.Import):
32
+ for alias in node.names:
33
+ name = alias.asname or alias.name.partition(".")[0]
34
+ target = alias.name if alias.asname else alias.name.partition(".")[0]
35
+ self._origins.setdefault(name, set()).add(target)
36
+ elif node.level == 0 and node.module is not None:
37
+ for alias in node.names:
38
+ self._origins.setdefault(alias.asname or alias.name, set()).add(f"{node.module}.{alias.name}")
39
+
40
+ def imported(self, expression: ast.expr, qualified: str) -> bool:
41
+ root = expression
42
+ while isinstance(root, ast.Attribute):
43
+ root = root.value
44
+ if not isinstance(root, ast.Name):
45
+ return False
46
+ if (
47
+ self.context.dynamic_namespace
48
+ or self.provenance.wildcard
49
+ or root.id in self.provenance.mutated
50
+ or root.id in self.provenance.external_bindings
51
+ ):
52
+ return False
53
+ binding = self.provenance.binding(root.id, expression)
54
+ if (
55
+ not isinstance(binding, ast.alias)
56
+ or not isinstance(self.provenance.scope(binding), ast.Module)
57
+ or import_name_for_binding(expression, binding, self.context.parents.get(binding)) != qualified
58
+ ):
59
+ return False
60
+ declaration = self.context.parents.get(binding)
61
+ if not isinstance(declaration, ast.Import | ast.ImportFrom) or not isinstance(
62
+ self.context.parents.get(declaration), ast.Module
63
+ ):
64
+ return False
65
+ if qualified not in self._namespace_safety:
66
+ self._namespace_safety[qualified] = self._namespace_untouched(qualified)
67
+ return self._namespace_safety[qualified]
68
+
69
+ def _namespace_untouched(self, qualified: str) -> bool:
70
+ for node in self.context.nodes(ast.Name, ast.Attribute):
71
+ targets = self._targets(node)
72
+ if not any(
73
+ target == qualified or target.startswith(qualified + ".") or qualified.startswith(target + ".")
74
+ for target in targets
75
+ ):
76
+ continue
77
+ if not isinstance(node.ctx, ast.Load) or not self._safe_api_read(node, qualified):
78
+ return False
79
+ return True
80
+
81
+ def _targets(self, expression: ast.Name | ast.Attribute) -> tuple[str, ...]:
82
+ parts: list[str] = []
83
+ root: ast.expr = expression
84
+ while isinstance(root, ast.Attribute):
85
+ parts.append(root.attr)
86
+ root = root.value
87
+ if not isinstance(root, ast.Name):
88
+ return ()
89
+ suffix = ".".join(reversed(parts))
90
+ return tuple(f"{origin}.{suffix}" if suffix else origin for origin in self._origins.get(root.id, ()))
91
+
92
+ def _safe_api_read(self, node: ast.Name | ast.Attribute, qualified: str) -> bool:
93
+ parent = self.context.parents.get(node)
94
+ if isinstance(parent, ast.Attribute) and parent.value is node:
95
+ return parent.attr not in _INTROSPECTION
96
+ if isinstance(parent, ast.Call) and parent.func is node:
97
+ return True
98
+ if isinstance(parent, ast.Subscript) and parent.value is node:
99
+ return True
100
+ if isinstance(parent, ast.arg | ast.AnnAssign) and parent.annotation is node:
101
+ return True
102
+ if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
103
+ return parent.returns is node or (
104
+ qualified == "contextlib.asynccontextmanager" and node in parent.decorator_list
105
+ )
106
+ return isinstance(parent, ast.ExceptHandler) and parent.type is node
107
+
108
+ def assignment(self, name: ast.Name, at: ast.AST) -> _LocalAssignment | None:
109
+ binding = self.provenance.binding(name.id, at)
110
+ if (
111
+ not isinstance(binding, ast.Name)
112
+ or name.id in self.provenance.mutated
113
+ or name.id in self.provenance.external_bindings
114
+ ):
115
+ return None
116
+ value = self.provenance.assignment_value(binding, at)
117
+ return _LocalAssignment(binding=binding, value=value) if value is not None else None
118
+
119
+
120
+ def method_receiver(call: ast.Call, methods: frozenset[str]) -> ast.Name | None:
121
+ callee = call.func
122
+ if isinstance(callee, ast.Attribute) and callee.attr in methods and isinstance(callee.value, ast.Name):
123
+ return callee.value
124
+ return None
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+
7
+ from sarj_python_lint.rules._resource_provenance import builtin_namespace_untouched
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from sarj_python_lint._file_context import PythonFileContext
12
+
13
+
14
+ _MAX_ALIAS_DEPTH = 8
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class LocalAssignment:
19
+ target: ast.Name
20
+ value: ast.expr
21
+
22
+
23
+ def native_builtin(context: PythonFileContext, node: ast.expr, name: str) -> bool:
24
+ return (
25
+ isinstance(node, ast.Name)
26
+ and node.id == name
27
+ and context.imports.builtin_is_unshadowed(name)
28
+ and builtin_namespace_untouched(context)
29
+ )
30
+
31
+
32
+ def imported_api(context: PythonFileContext, node: ast.expr, qualified: str) -> bool:
33
+ if context.imports.resolved_qualified_name(node) != qualified:
34
+ return False
35
+ provenance = context.static_resource_provenance
36
+ root = node
37
+ while isinstance(root, ast.Attribute):
38
+ root = root.value
39
+ if not isinstance(root, ast.Name):
40
+ return False
41
+ binding = provenance.binding(root.id, node)
42
+ if not isinstance(binding, ast.alias) or provenance.scope(binding) is not context.tree:
43
+ return False
44
+ if root.id in provenance.mutated:
45
+ return False
46
+ module = qualified.partition(".")[0]
47
+ if not _module_namespace_intact(context, module):
48
+ return False
49
+ for read in provenance.binding_reads(root.id, binding):
50
+ parent = context.parents.get(read)
51
+ if not (isinstance(parent, ast.Attribute) or (isinstance(parent, ast.Call) and parent.func is read)):
52
+ return False
53
+ for reference in context.nodes(ast.Name, ast.Attribute):
54
+ if context.imports.resolved_qualified_name(reference) != qualified:
55
+ continue
56
+ parent = context.parents.get(reference)
57
+ if not isinstance(parent, ast.Call) or parent.func is not reference:
58
+ return False
59
+ return True
60
+
61
+
62
+ def _module_namespace_intact(context: PythonFileContext, module: str) -> bool:
63
+ for member in context.nodes(ast.Attribute):
64
+ target = context.imports.resolved_qualified_name(member)
65
+ if isinstance(member.ctx, ast.Store | ast.Del) and target is not None and target.startswith(module + "."):
66
+ return False
67
+ for statement in context.nodes(ast.Import):
68
+ for alias in statement.names:
69
+ if alias.name != module:
70
+ continue
71
+ if not _namespace_reads_are_members(context, alias):
72
+ return False
73
+ return True
74
+
75
+
76
+ def _namespace_reads_are_members(context: PythonFileContext, alias: ast.alias) -> bool:
77
+ local = alias.asname or alias.name
78
+ for read in context.static_resource_provenance.binding_reads(local, alias):
79
+ parent = context.parents.get(read)
80
+ if not isinstance(parent, ast.Attribute) or parent.value is not read:
81
+ return False
82
+ return True
83
+
84
+
85
+ def local_value(context: PythonFileContext, node: ast.Name) -> ast.expr | None:
86
+ provenance = context.static_resource_provenance
87
+ binding = provenance.binding(node.id, node)
88
+ if not isinstance(binding, ast.Name):
89
+ return None
90
+ return provenance.assignment_value(binding, node)
91
+
92
+
93
+ def value_origin(context: PythonFileContext, expression: ast.expr, *, depth: int = 0) -> str:
94
+ if isinstance(expression, ast.Name):
95
+ value = local_value(context, expression)
96
+ if isinstance(value, ast.Name) and depth < _MAX_ALIAS_DEPTH:
97
+ return value_origin(context, value, depth=depth + 1)
98
+ binding = context.static_resource_provenance.binding(expression.id, expression)
99
+ if isinstance(binding, ast.Name | ast.arg):
100
+ return f"binding:{binding.lineno}:{binding.col_offset}"
101
+ return ast.dump(expression)
102
+
103
+
104
+ def overridden_exit(context: PythonFileContext, node: ast.AST) -> bool:
105
+ parent = context.parents.get(node)
106
+ while parent is not None and not isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
107
+ if isinstance(parent, ast.Try | ast.TryStar) and any(
108
+ isinstance(nested, ast.Return | ast.Raise | ast.Break | ast.Continue)
109
+ for statement in parent.finalbody
110
+ for nested in ast.walk(statement)
111
+ ):
112
+ return True
113
+ parent = context.parents.get(parent)
114
+ return False
115
+
116
+
117
+ def local_function(context: PythonFileContext, node: ast.expr) -> ast.FunctionDef | None:
118
+ if not isinstance(node, ast.Name):
119
+ return None
120
+ provenance = context.static_resource_provenance
121
+ binding = provenance.binding(node.id, node)
122
+ if not isinstance(binding, ast.FunctionDef) or binding.decorator_list:
123
+ return None
124
+ if provenance.scope(binding) is not context.tree or node.id in provenance.mutated:
125
+ return None
126
+ for read in provenance.binding_reads(node.id, binding):
127
+ parent = context.parents.get(read)
128
+ if not isinstance(parent, ast.Call) or parent.func is not read:
129
+ return None
130
+ return binding
131
+
132
+
133
+ def function_body(function: ast.FunctionDef) -> list[ast.stmt]:
134
+ body = function.body
135
+ if (
136
+ body
137
+ and isinstance(body[0], ast.Expr)
138
+ and isinstance(body[0].value, ast.Constant)
139
+ and isinstance(body[0].value.value, str)
140
+ ):
141
+ return body[1:]
142
+ return body
143
+
144
+
145
+ def assignment(statement: ast.stmt) -> LocalAssignment | None:
146
+ match statement:
147
+ case ast.Assign(targets=[ast.Name() as target], value=value):
148
+ return LocalAssignment(target=target, value=value)
149
+ case ast.AnnAssign(target=ast.Name() as target, value=ast.expr() as value):
150
+ return LocalAssignment(target=target, value=value)
151
+ case _:
152
+ return None
153
+
154
+
155
+ def null_test(expression: ast.expr, name: str) -> bool:
156
+ return (
157
+ isinstance(expression, ast.Compare)
158
+ and isinstance(expression.left, ast.Name)
159
+ and expression.left.id == name
160
+ and len(expression.ops) == 1
161
+ and isinstance(expression.ops[0], ast.Is)
162
+ and len(expression.comparators) == 1
163
+ and isinstance(expression.comparators[0], ast.Constant)
164
+ and expression.comparators[0].value is None
165
+ )
166
+
167
+
168
+ def returns_none(statement: ast.stmt) -> bool:
169
+ return isinstance(statement, ast.Return) and (
170
+ statement.value is None or (isinstance(statement.value, ast.Constant) and statement.value.value is None)
171
+ )
@@ -216,10 +216,15 @@ if TYPE_CHECKING:
216
216
  from collections.abc import Mapping
217
217
 
218
218
  from sarj_python_lint.rule_base import Rule
219
+ from sarj_python_lint.rules.async_executor_shutdown import AsyncExecutorShutdown
220
+ from sarj_python_lint.rules.async_subprocess_cancellation_cleanup import AsyncSubprocessCancellationCleanup
221
+ from sarj_python_lint.rules.cancel_owned_task_without_join import CancelOwnedTaskWithoutJoin
219
222
  from sarj_python_lint.rules.delete_route_returns_no_content import DeleteRouteReturnsNoContent
220
223
  from sarj_python_lint.rules.duplicate_adjacent_exception_handler import DuplicateAdjacentExceptionHandler
221
224
  from sarj_python_lint.rules.duplicate_closed_pure_helper import DuplicateClosedPureHelper
222
225
  from sarj_python_lint.rules.duplicate_pytest_fixture_params import DuplicatePytestFixtureParams
226
+ from sarj_python_lint.rules.failed_probe_zero_threshold import FailedProbeZeroThreshold
227
+ from sarj_python_lint.rules.nan_hidden_by_builtin_min import NanHiddenByBuiltinMin
223
228
  from sarj_python_lint.rules.no_conflicting_pydantic_alias_validation import NoConflictingPydanticAliasValidation
224
229
  from sarj_python_lint.rules.no_contradictory_type_guard import NoContradictoryTypeGuard
225
230
  from sarj_python_lint.rules.no_create_verb_in_post_path import NoCreateVerbInPostPath
@@ -256,6 +261,11 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
256
261
 
257
262
  REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
258
263
  {
264
+ AsyncSubprocessCancellationCleanup.id: AsyncSubprocessCancellationCleanup,
265
+ CancelOwnedTaskWithoutJoin.id: CancelOwnedTaskWithoutJoin,
266
+ AsyncExecutorShutdown.id: AsyncExecutorShutdown,
267
+ NanHiddenByBuiltinMin.id: NanHiddenByBuiltinMin,
268
+ FailedProbeZeroThreshold.id: FailedProbeZeroThreshold,
259
269
  PreferGroupedSettings.id: PreferGroupedSettings,
260
270
  NoRedundantLocalAlias.id: NoRedundantLocalAlias,
261
271
  PytestFixtureReturnsClosedResource.id: PytestFixtureReturnsClosedResource,
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, ClassVar, final, override
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ )
19
+ from sarj_python_lint.rules._async_lifecycle import AsyncApiProof, method_receiver
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+
25
+
26
+ _EXECUTORS = ("concurrent.futures.ThreadPoolExecutor", "concurrent.futures.ProcessPoolExecutor")
27
+ _BAD = "from concurrent.futures import ThreadPoolExecutor\n\nasync def close():\n executor = ThreadPoolExecutor()\n executor.shutdown(wait=True)\n"
28
+ _GOOD = "import asyncio\nfrom concurrent.futures import ThreadPoolExecutor\n\nasync def close():\n executor = ThreadPoolExecutor()\n await asyncio.to_thread(executor.shutdown, wait=True)\n"
29
+
30
+
31
+ @final
32
+ class AsyncExecutorShutdown(Rule):
33
+ id = "async-executor-shutdown"
34
+ code = "SARJ540"
35
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
36
+ default_level=Severity.WARNING,
37
+ summary="Run waiting executor shutdown outside the async event-loop thread.",
38
+ rationale="Executor.shutdown waits for workers by default; invoking it directly in an async function can block the event loop and delay unrelated service work or cancellation.",
39
+ remediation="Await asyncio.to_thread(executor.shutdown, wait=True), or use the application's explicit nonblocking shutdown policy after arranging worker ownership and completion.",
40
+ category=RuleCategory.CORRECTNESS,
41
+ autofix=AutofixPolicy.NONE,
42
+ limitations=(
43
+ "Requires a stable direct assignment from an import-proven concurrent.futures ThreadPoolExecutor or ProcessPoolExecutor constructor and a direct shutdown call in an executing async function body.",
44
+ "Only omitted wait or literal wait=True is reported. Literal wait=False, dynamic arguments, nested synchronous callbacks, custom factories, borrowed receivers, context-managed bindings and explicit receiver/API mutation are excluded.",
45
+ "Stable enclosing-function and module bindings are recognized. Ordinary argument passing does not erase the constructor identity; external monkey-patching inside opaque consumers and runtime replacement of stdlib modules are not inferred.",
46
+ "Unknown bound-method consumers, receiver aliases/storage, conditional imports, namespace escapes, reflection, wildcard imports and generated files conservatively abstain. No automatic shutdown-policy change is offered.",
47
+ ),
48
+ examples=(
49
+ RuleExample(
50
+ example_id="waiting-shutdown-in-async",
51
+ title="Waiting shutdown runs on the event-loop thread",
52
+ outcome=ExampleOutcome.MATCH,
53
+ files=(ExampleFile.python("app/lifecycle.py", _BAD),),
54
+ focus_path=PurePosixPath("app/lifecycle.py"),
55
+ expected_count=1,
56
+ public=True,
57
+ ),
58
+ RuleExample(
59
+ example_id="shutdown-in-worker-thread",
60
+ title="Await waiting shutdown outside the event-loop thread",
61
+ outcome=ExampleOutcome.NO_MATCH,
62
+ files=(ExampleFile.python("app/lifecycle.py", _GOOD),),
63
+ focus_path=PurePosixPath("app/lifecycle.py"),
64
+ expected_count=0,
65
+ public=True,
66
+ ),
67
+ ),
68
+ )
69
+ description = documentation.summary
70
+
71
+ @override
72
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
73
+ if "shutdown" not in context.symbol_source or context.generated or context.tree is None:
74
+ return []
75
+ proof = AsyncApiProof(context)
76
+ diagnostics: list[Diagnostic] = []
77
+ for call in context.nodes(ast.Call):
78
+ receiver = method_receiver(call, frozenset({"shutdown"}))
79
+ if receiver is None or not isinstance(proof.provenance.scope(call), ast.AsyncFunctionDef):
80
+ continue
81
+ if not _waiting_shutdown(call):
82
+ continue
83
+ assigned = proof.assignment(receiver, call)
84
+ if assigned is None:
85
+ continue
86
+ binding = assigned.binding
87
+ value = assigned.value
88
+ if not isinstance(value, ast.Call) or not any(proof.imported(value.func, name) for name in _EXECUTORS):
89
+ continue
90
+ if not _unchanged_receiver(proof, binding) or is_suppressed(context.source_lines, call.lineno, self.code):
91
+ continue
92
+ diagnostics.append(
93
+ Diagnostic(
94
+ path=context.path,
95
+ line=call.lineno,
96
+ col=call.col_offset + 1,
97
+ code=self.code,
98
+ severity=Severity.WARNING,
99
+ message="waiting Executor.shutdown runs on the event-loop thread; await an offloaded shutdown after arranging worker ownership",
100
+ )
101
+ )
102
+ return diagnostics
103
+
104
+
105
+ def _waiting_shutdown(call: ast.Call) -> bool:
106
+ if len(call.args) > 1 or any(keyword.arg not in {"wait", "cancel_futures"} for keyword in call.keywords):
107
+ return False
108
+ values = [*call.args, *(keyword.value for keyword in call.keywords if keyword.arg == "wait")]
109
+ return not values or (len(values) == 1 and isinstance(values[0], ast.Constant) and values[0].value is True)
110
+
111
+
112
+ def _unchanged_receiver(proof: AsyncApiProof, binding: ast.Name) -> bool:
113
+ for read in proof.provenance.binding_reads(binding.id, binding):
114
+ parent = proof.context.parents.get(read)
115
+ if isinstance(parent, ast.Call) and read in parent.args:
116
+ continue
117
+ if isinstance(parent, ast.keyword) and isinstance(proof.context.parents.get(parent), ast.Call):
118
+ continue
119
+ if (
120
+ not isinstance(parent, ast.Attribute)
121
+ or parent.value is not read
122
+ or parent.attr in {"__dict__", "__setattr__", "__delattr__", "__getattribute__"}
123
+ ):
124
+ return False
125
+ consumer = proof.context.parents.get(parent)
126
+ if isinstance(consumer, ast.Call) and consumer.func is parent:
127
+ continue
128
+ if (
129
+ isinstance(consumer, ast.Call)
130
+ and parent in consumer.args
131
+ and proof.imported(consumer.func, "asyncio.to_thread")
132
+ ):
133
+ continue
134
+ return False
135
+ return True