sarj-python-lint 1.66.0__tar.gz → 1.66.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/pyproject.toml +1 -1
  3. sarj_python_lint-1.66.1/src/sarj_python_lint/rules/_ast_names.py +63 -0
  4. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_closed_helpers.py +10 -0
  5. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_comments.py +30 -4
  6. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_docstrings.py +19 -0
  7. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +1 -8
  8. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +2 -23
  9. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +2 -21
  10. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +1 -6
  11. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +2 -8
  12. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +1 -8
  13. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -9
  14. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +1 -6
  15. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_partial_type_is.py +2 -8
  16. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -8
  17. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +1 -7
  18. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_select_star.py +1 -8
  19. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -8
  20. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +1 -8
  21. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +1 -6
  22. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/over_mocked_test.py +1 -12
  23. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_class_row.py +1 -6
  24. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +2 -12
  25. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +6 -24
  26. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +20 -11
  27. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +2 -4
  28. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +2 -4
  29. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +1 -6
  30. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +2 -4
  31. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +1 -8
  32. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +1 -6
  33. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +1 -8
  34. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +2 -11
  35. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +1 -15
  36. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +1 -8
  37. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -9
  38. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +2 -20
  39. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +2 -17
  40. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +1 -13
  41. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +1 -6
  42. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/restated_test_docstring.py +11 -25
  43. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +1 -10
  44. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/typed_error_reasons.py +1 -8
  45. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +1 -15
  46. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/unused_mock_setup.py +1 -12
  47. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/.gitignore +0 -0
  48. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/LICENSE +0 -0
  49. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/README.md +0 -0
  50. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/__init__.py +0 -0
  51. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/__main__.py +0 -0
  52. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  53. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_file_context.py +0 -0
  54. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_filesystem.py +0 -0
  55. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_python_target.py +0 -0
  56. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  57. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_secret_names.py +0 -0
  58. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_source.py +0 -0
  59. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/_version.py +0 -0
  60. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/benchmark.py +0 -0
  61. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/cast_access.py +0 -0
  62. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
  63. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/json_boundary.py +0 -0
  64. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/py.typed +0 -0
  65. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/ratchet.py +0 -0
  66. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rule_base.py +0 -0
  67. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  68. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  69. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  70. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  71. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  72. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  73. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  74. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  75. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_exports.py +0 -0
  76. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  77. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  78. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  79. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  80. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  81. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  82. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  83. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  84. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  85. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  86. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  87. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  88. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  89. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  90. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  91. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  92. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  93. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_registry.py +0 -0
  94. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  95. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  96. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  97. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  98. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  99. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  100. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  101. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  102. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  103. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  104. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  105. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  106. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  107. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  108. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  109. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  110. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  111. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  112. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  113. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  114. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  115. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  116. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  117. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  118. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  119. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  120. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  121. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  122. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  123. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  124. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  125. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  126. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  127. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  128. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  129. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  130. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  131. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  132. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  133. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  134. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  135. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  136. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  137. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  138. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  139. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  140. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  141. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  142. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  143. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  144. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  145. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  146. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  147. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  148. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  149. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  150. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  151. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  152. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  153. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  154. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  155. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  156. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  157. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  158. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  159. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  160. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  161. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  162. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  163. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  164. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  165. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  166. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  167. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  168. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  169. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  170. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  171. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  172. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  173. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  174. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  175. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  176. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  177. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  178. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  179. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  180. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  181. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  182. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  183. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  184. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  185. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  186. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  187. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  188. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  189. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  190. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  191. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  192. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  193. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  194. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  195. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  196. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  197. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  198. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  199. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  200. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  201. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  202. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  203. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  204. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  205. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  206. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  207. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  208. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  209. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  210. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  211. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  212. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  213. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  214. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  215. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  216. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  217. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  218. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  219. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  220. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  221. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  222. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  223. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  224. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  225. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  226. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  227. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  228. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  229. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  230. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  231. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  232. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  233. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  234. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  235. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  236. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  237. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  238. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  239. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  240. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  241. {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.1}/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.66.0
3
+ Version: 1.66.1
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.66.0"
3
+ version = "1.66.1"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+
5
+
6
+ def root_name(node: ast.expr) -> str | None:
7
+ while isinstance(node, ast.Attribute):
8
+ node = node.value
9
+ return node.id if isinstance(node, ast.Name) else None
10
+
11
+
12
+ def expression_root(node: ast.expr) -> str | None:
13
+ while isinstance(node, (ast.Attribute, ast.Subscript)):
14
+ node = node.value
15
+ return node.id if isinstance(node, ast.Name) else None
16
+
17
+
18
+ def terminal_name(node: ast.expr) -> str | None:
19
+ match node:
20
+ case ast.Name(id=name) | ast.Attribute(attr=name):
21
+ return name
22
+ case _:
23
+ return None
24
+
25
+
26
+ def unqualified_name(node: ast.expr) -> str:
27
+ match node:
28
+ case ast.Name(id=name) | ast.Attribute(attr=name):
29
+ return name
30
+ case _:
31
+ return ""
32
+
33
+
34
+ def dotted_parts(node: ast.expr) -> tuple[str, ...] | None:
35
+ parts: list[str] = []
36
+ current = node
37
+ while isinstance(current, ast.Attribute):
38
+ parts.append(current.attr)
39
+ current = current.value
40
+ if not isinstance(current, ast.Name):
41
+ return None
42
+ parts.append(current.id)
43
+ return tuple(reversed(parts))
44
+
45
+
46
+ def dotted_name(node: ast.expr) -> str | None:
47
+ parts = dotted_parts(node)
48
+ return ".".join(parts) if parts is not None else None
49
+
50
+
51
+ def assignment_names(node: ast.AST) -> set[str]:
52
+ match node:
53
+ case ast.Name(id=name):
54
+ return {name}
55
+ case ast.Tuple() | ast.List():
56
+ names: set[str] = set()
57
+ for element in node.elts:
58
+ names.update(assignment_names(element))
59
+ return names
60
+ case ast.Starred(value=value):
61
+ return assignment_names(value)
62
+ case _:
63
+ return set()
@@ -183,3 +183,13 @@ def _plain_decorator(
183
183
  and not (keyword.arg == "mode" and isinstance(keyword.value, ast.Constant) and keyword.value.value == "wrap")
184
184
  for keyword in decorator.keywords
185
185
  )
186
+
187
+
188
+ def closed_owner(function: ast.FunctionDef | ast.AsyncFunctionDef, context: PythonFileContext) -> bool:
189
+ if not isinstance(function, ast.FunctionDef) or not function.name.startswith("_"):
190
+ return False
191
+ references = closed_references(context, function, context.resource_provenance)
192
+ return bool(references) and all(
193
+ (call := direct_call(reference, context)) is not None and call_matches(function, call)
194
+ for reference in references
195
+ )
@@ -326,7 +326,13 @@ class _Scan(NamedTuple):
326
326
  ordered: _Ordered
327
327
 
328
328
 
329
- _last_scan: tuple[str, _Scan] | None = None
329
+ class _ScanMemo(NamedTuple):
330
+ source: str
331
+ scan: _Scan | None
332
+ comment_lines: frozenset[int] | None
333
+
334
+
335
+ _last_scan: _ScanMemo | None = None
330
336
 
331
337
 
332
338
  def all_comments(source: str) -> _CommentScan[_Ordered]:
@@ -334,15 +340,35 @@ def all_comments(source: str) -> _CommentScan[_Ordered]:
334
340
  return _CommentScan(scan.ordered, scan.first_code_line)
335
341
 
336
342
 
343
+ def comment_line_numbers(source: str) -> frozenset[int]:
344
+ global _last_scan # ruff: ignore[global-statement] -- retain the existing single-source memo for both comment projections.
345
+ cached = _last_scan
346
+ scan = cached.scan if cached is not None and cached.source is source else None
347
+ if cached is not None and cached.source is source and cached.comment_lines is not None:
348
+ return cached.comment_lines
349
+ lines: frozenset[int]
350
+ if scan is not None:
351
+ lines = frozenset(comment.line for comment in scan.ordered)
352
+ else:
353
+ try:
354
+ tokens = tokenize.generate_tokens(io.StringIO(source).readline)
355
+ lines = frozenset(token.start[0] for token in tokens if token.type == tokenize.COMMENT)
356
+ except IndentationError, tokenize.TokenError:
357
+ lines = frozenset()
358
+ _last_scan = _ScanMemo(source, scan, lines)
359
+ return lines
360
+
361
+
337
362
  def _scan_memo(source: str) -> _Scan:
338
363
  global _last_scan # ruff: ignore[global-statement] — single-slot memo; the CLI runs rules per file sequentially
339
- if _last_scan is not None and _last_scan[0] is source:
340
- return _last_scan[1]
364
+ cached = _last_scan if _last_scan is not None and _last_scan.source is source else None
365
+ if cached is not None and cached.scan is not None:
366
+ return cached.scan
341
367
  try:
342
368
  result = _scan(source)
343
369
  except UnicodeError:
344
370
  result = _Scan([], [], set(), 1 << 30, [])
345
- _last_scan = (source, result)
371
+ _last_scan = _ScanMemo(source, result, cached.comment_lines if cached is not None else None)
346
372
  return result
347
373
 
348
374
 
@@ -13,9 +13,16 @@ if TYPE_CHECKING:
13
13
  from collections.abc import Iterable
14
14
 
15
15
  from sarj_python_lint.rules._ast_index import NodeIndex
16
+ from sarj_python_lint.rules._imports import ImportIndex
16
17
 
17
18
 
18
19
  _GETATTR_MIN_ARGS = 2
20
+ _RUNTIME_DOC_DECORATORS = (
21
+ (frozenset({"agents"}), "function_tool"),
22
+ (frozenset({"click"}), "command"),
23
+ (frozenset({"typer"}), "command"),
24
+ )
25
+ _OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
19
26
 
20
27
  # Docstring filler that says nothing about *which* thing is being described.
21
28
  _BASE_STOPWORDS = frozenset(
@@ -259,6 +266,18 @@ def decorator_markers(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDe
259
266
  return markers
260
267
 
261
268
 
269
+ def is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
270
+ if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
271
+ return True
272
+ for decorator in node.decorator_list:
273
+ target = decorator.func if isinstance(decorator, ast.Call) else decorator
274
+ if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
275
+ return True
276
+ if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
277
+ return True
278
+ return False
279
+
280
+
262
281
  def docstring_expression(
263
282
  node: ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
264
283
  ) -> ast.Expr | None:
@@ -17,6 +17,7 @@ from sarj_python_lint.rule_base import (
17
17
  Severity,
18
18
  )
19
19
  from sarj_python_lint.rules._ast_index import nodes, walk
20
+ from sarj_python_lint.rules._ast_names import unqualified_name as _call_name
20
21
  from sarj_python_lint.rules._paths import is_test_path
21
22
  from sarj_python_lint.rules._sql import is_store_module, sql_string_value, strip_sql_noise
22
23
  from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import analytical_signal
@@ -148,14 +149,6 @@ def _query_argument(call: ast.Call) -> ast.expr | None:
148
149
  return next((keyword.value for keyword in call.keywords if keyword.arg in _QUERY_KEYWORDS), None)
149
150
 
150
151
 
151
- def _call_name(function: ast.expr) -> str:
152
- match function:
153
- case ast.Name(id=name) | ast.Attribute(attr=name):
154
- return name
155
- case _:
156
- return ""
157
-
158
-
159
152
  def _postgres_sql_constructors(
160
153
  tree: ast.Module, parents: Mapping[ast.AST, ast.AST], *, node_index: NodeIndex | None = None
161
154
  ) -> frozenset[_ConstructorImport]:
@@ -20,13 +20,12 @@ from sarj_python_lint.rule_base import (
20
20
  from sarj_python_lint.rules._ast_index import children
21
21
  from sarj_python_lint.rules._comments import is_protected, stem
22
22
  from sarj_python_lint.rules._docstrings import (
23
- PROMPT_DECORATOR_MARKERS,
24
23
  VALUE_MARKER_RE,
25
24
  annotation_tokens,
26
25
  arg_entries,
27
26
  arg_section,
28
- decorator_markers,
29
27
  identifier_stems,
28
+ is_runtime_consumed_or_overload,
30
29
  restates,
31
30
  )
32
31
 
@@ -40,14 +39,6 @@ if TYPE_CHECKING:
40
39
 
41
40
  _ARGUMENT_CONSTRAINT_RE = re.compile(r"\b(available|current|existing|optional|required|supported)\b", re.IGNORECASE)
42
41
 
43
- _RUNTIME_DOC_DECORATORS = (
44
- (frozenset({"agents"}), "function_tool"),
45
- (frozenset({"click"}), "command"),
46
- (frozenset({"typer"}), "command"),
47
- )
48
-
49
- _OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
50
-
51
42
 
52
43
  @dataclass(frozen=True, slots=True)
53
44
  class _ParameterFacts:
@@ -151,7 +142,7 @@ class DocstringArgsRestateSignature(Rule):
151
142
  block = arg_section(docstring)
152
143
  if block is None or VALUE_MARKER_RE.search(block) or is_protected(block):
153
144
  return
154
- if _is_runtime_consumed_or_overload(node, imports):
145
+ if is_runtime_consumed_or_overload(node, imports):
155
146
  return
156
147
  entries = arg_entries(block)
157
148
  if not entries:
@@ -202,15 +193,3 @@ def _parameter_stems(
202
193
  known = callable_stems | identifier_stems(argument.arg) | annotation_stems
203
194
  parameters[argument.arg] = _ParameterFacts(frozenset(known), frozenset(annotation_stems))
204
195
  return parameters
205
-
206
-
207
- def _is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
208
- if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
209
- return True
210
- for decorator in node.decorator_list:
211
- target = decorator.func if isinstance(decorator, ast.Call) else decorator
212
- if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
213
- return True
214
- if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
215
- return True
216
- return False
@@ -20,11 +20,10 @@ from sarj_python_lint.rule_base import (
20
20
  from sarj_python_lint.rules._ast_index import children
21
21
  from sarj_python_lint.rules._comments import is_protected, stem
22
22
  from sarj_python_lint.rules._docstrings import (
23
- PROMPT_DECORATOR_MARKERS,
24
23
  VALUE_MARKER_RE,
25
24
  annotation_tokens,
26
- decorator_markers,
27
25
  identifier_stems,
26
+ is_runtime_consumed_or_overload,
28
27
  restates,
29
28
  sections,
30
29
  )
@@ -51,12 +50,6 @@ _RESULT_SEMANTIC_RE = re.compile(
51
50
  re.IGNORECASE,
52
51
  )
53
52
 
54
- _RUNTIME_DOC_DECORATORS = (
55
- (frozenset({"agents"}), "function_tool"),
56
- (frozenset({"click"}), "command"),
57
- (frozenset({"typer"}), "command"),
58
- )
59
- _OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
60
53
  _GENERATOR_TYPES = frozenset({"AsyncGenerator", "AsyncIterable", "AsyncIterator", "Generator", "Iterable", "Iterator"})
61
54
  _BROAD_RETURN_TYPES = frozenset({"Any", "object"})
62
55
 
@@ -228,7 +221,7 @@ class DocstringReturnsRestateSignature(Rule):
228
221
  or _TYPED_RESULT_RE.search(block)
229
222
  ):
230
223
  return
231
- if _is_runtime_consumed_or_overload(node, imports):
224
+ if is_runtime_consumed_or_overload(node, imports):
232
225
  return
233
226
  annotation = _result_annotation(node, section.name)
234
227
  if annotation is None or _is_broad_annotation(annotation):
@@ -313,15 +306,3 @@ def _contains_yield(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
313
306
 
314
307
  def _is_broad_annotation(annotation: ast.expr) -> bool:
315
308
  return _annotation_name(annotation) in _BROAD_RETURN_TYPES
316
-
317
-
318
- def _is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
319
- if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
320
- return True
321
- for decorator in node.decorator_list:
322
- target = decorator.func if isinstance(decorator, ast.Call) else decorator
323
- if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
324
- return True
325
- if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
326
- return True
327
- return False
@@ -21,6 +21,7 @@ from sarj_python_lint.rule_base import (
21
21
  )
22
22
  from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
23
23
  from sarj_python_lint.rules._ast_index import walk as walk_ast
24
+ from sarj_python_lint.rules._ast_names import expression_root as _expression_root
24
25
  from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
25
26
 
26
27
 
@@ -568,12 +569,6 @@ def _type_attribute_mutation(call: ast.Call, imports: ImportIndex, sensitive: se
568
569
  return known and _expression_root(call.args[0]) in sensitive
569
570
 
570
571
 
571
- def _expression_root(node: ast.expr) -> str | None:
572
- while isinstance(node, (ast.Attribute, ast.Subscript)):
573
- node = node.value
574
- return node.id if isinstance(node, ast.Name) else None
575
-
576
-
577
572
  def _unshadowed_type_expression(
578
573
  node: ast.expr, parents: Mapping[ast.AST, ast.AST], scope_cache: dict[int, frozenset[str]]
579
574
  ) -> bool:
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._closed_helpers import executable_body
19
20
  from sarj_python_lint.rules._imports import TYPING_SOURCES
20
21
  from sarj_python_lint.rules._type_guard_symbols import (
21
22
  builtin_guard_symbol,
@@ -189,14 +190,7 @@ def _sole_parameter(function: ast.FunctionDef) -> ast.arg | None:
189
190
 
190
191
 
191
192
  def _single_return(function: ast.FunctionDef) -> ast.Return | None:
192
- body = function.body
193
- if (
194
- body
195
- and isinstance(body[0], ast.Expr)
196
- and isinstance(body[0].value, ast.Constant)
197
- and isinstance(body[0].value.value, str)
198
- ):
199
- body = body[1:]
193
+ body = executable_body(function)
200
194
  return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
201
195
 
202
196
 
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
15
15
  RuleExample,
16
16
  )
17
17
  from sarj_python_lint.rules._ast_index import walk as walk_ast
18
+ from sarj_python_lint.rules._ast_names import unqualified_name as _annotation_name
18
19
  from sarj_python_lint.rules._exports import mentions_dunder_all
19
20
 
20
21
 
@@ -164,14 +165,6 @@ def _dunder_all_matches_primary(tree: ast.Module, primary_name: str) -> bool:
164
165
  return isinstance(entry, ast.Constant) and entry.value == primary_name
165
166
 
166
167
 
167
- def _annotation_name(node: ast.expr) -> str:
168
- match node:
169
- case ast.Name(id=name) | ast.Attribute(attr=name):
170
- return name
171
- case _:
172
- return ""
173
-
174
-
175
168
  def _is_skipped_path(path: Path) -> bool:
176
169
  return "tests" in path.parts
177
170
 
@@ -368,15 +368,6 @@ def _directly_bound_names(statement: ast.stmt) -> set[str]:
368
368
  return set()
369
369
 
370
370
 
371
- def _argument_names(arguments: ast.arguments) -> set[str]:
372
- names = {argument.arg for argument in (*arguments.posonlyargs, *arguments.args, *arguments.kwonlyargs)}
373
- if arguments.vararg is not None:
374
- names.add(arguments.vararg.arg)
375
- if arguments.kwarg is not None:
376
- names.add(arguments.kwarg.arg)
377
- return names
378
-
379
-
380
371
  def _target_names(target: ast.expr) -> set[str]:
381
372
  if isinstance(target, ast.Name):
382
373
  return {target.id}
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
14
14
  RuleDocumentation,
15
15
  RuleExample,
16
16
  )
17
+ from sarj_python_lint.rules._ast_names import root_name as _root_name
17
18
  from sarj_python_lint.rules._imports import ImportIndex
18
19
  from sarj_python_lint.rules._paths import is_test_path
19
20
 
@@ -237,12 +238,6 @@ def _shadowed_in_enclosing_function(
237
238
  return False
238
239
 
239
240
 
240
- def _root_name(node: ast.expr) -> str | None:
241
- while isinstance(node, ast.Attribute):
242
- node = node.value
243
- return node.id if isinstance(node, ast.Name) else None
244
-
245
-
246
241
  def _function_binds(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda, name: str) -> bool:
247
242
  arguments = node.args
248
243
  if (
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._closed_helpers import executable_body
19
20
  from sarj_python_lint.rules._imports import TYPING_SOURCES
20
21
  from sarj_python_lint.rules._type_guard_symbols import (
21
22
  builtin_guard_symbol,
@@ -211,14 +212,7 @@ def _partial_return(function: ast.FunctionDef, context: PythonFileContext) -> as
211
212
 
212
213
 
213
214
  def _single_return(function: ast.FunctionDef) -> ast.Return | None:
214
- body = function.body
215
- if (
216
- body
217
- and isinstance(body[0], ast.Expr)
218
- and isinstance(body[0].value, ast.Constant)
219
- and isinstance(body[0].value.value, str)
220
- ):
221
- body = body[1:]
215
+ body = executable_body(function)
222
216
  return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
223
217
 
224
218
 
@@ -951,14 +951,6 @@ def _scope_bindings(function: ast.FunctionDef | ast.AsyncFunctionDef) -> frozens
951
951
  return frozenset(names)
952
952
 
953
953
 
954
- def _target_names(node: ast.expr) -> set[str]:
955
- if isinstance(node, ast.Name):
956
- return {node.id}
957
- if isinstance(node, ast.Tuple | ast.List):
958
- return {name for element in node.elts for name in _target_names(element)}
959
- return set()
960
-
961
-
962
954
  def _position(node: ast.AST) -> AstPosition:
963
955
  return ast_position(node, missing=-1)
964
956
 
@@ -20,6 +20,7 @@ from sarj_python_lint.rule_base import (
20
20
  is_suppressed,
21
21
  )
22
22
  from sarj_python_lint.rules._ast_index import comprehension_results, walk as walk_ast
23
+ from sarj_python_lint.rules._ast_names import root_name as _root_name
23
24
  from sarj_python_lint.rules._imports import ImportIndex
24
25
  from sarj_python_lint.rules._paths import is_test_path
25
26
 
@@ -984,13 +985,6 @@ def _api_resolves(
984
985
  return root is not None and root not in api.shadowed and api.imports.resolves(node, sources=sources, symbol=symbol)
985
986
 
986
987
 
987
- def _root_name(node: ast.expr) -> str | None:
988
- current = node
989
- while isinstance(current, ast.Attribute):
990
- current = current.value
991
- return current.id if isinstance(current, ast.Name) else None
992
-
993
-
994
988
  def _is_source_open(
995
989
  node: ast.expr,
996
990
  path_names: set[str],
@@ -17,6 +17,7 @@ from sarj_python_lint.rule_base import (
17
17
  RuleExample,
18
18
  )
19
19
  from sarj_python_lint.rules._ast_index import walk
20
+ from sarj_python_lint.rules._ast_names import unqualified_name as _call_name
20
21
  from sarj_python_lint.rules._paths import is_test_path
21
22
  from sarj_python_lint.rules._sql import POSTGRES_UNQUOTED_IDENTIFIER, sql_string_value, strip_sql_noise
22
23
 
@@ -280,11 +281,3 @@ def _is_query_binding(target: ast.expr) -> bool:
280
281
  name = target.id if isinstance(target, ast.Name) else target.attr
281
282
  tokens = frozenset(part for part in name.lower().split("_") if part)
282
283
  return name == "q" or name.isupper() or bool(tokens & _QUERY_BINDING_TOKENS)
283
-
284
-
285
- def _call_name(function: ast.expr) -> str:
286
- match function:
287
- case ast.Name(id=name) | ast.Attribute(attr=name):
288
- return name
289
- case _:
290
- return ""
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._closed_helpers import executable_body
19
20
  from sarj_python_lint.rules._imports import TYPING_SOURCES
20
21
  from sarj_python_lint.rules._type_guard_symbols import (
21
22
  builtin_guard_symbol,
@@ -197,14 +198,7 @@ def _guard_record(
197
198
 
198
199
 
199
200
  def _single_return(function: ast.FunctionDef) -> ast.Return | None:
200
- body = function.body
201
- if (
202
- body
203
- and isinstance(body[0], ast.Expr)
204
- and isinstance(body[0].value, ast.Constant)
205
- and isinstance(body[0].value.value, str)
206
- ):
207
- body = body[1:]
201
+ body = executable_body(function)
208
202
  return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
209
203
 
210
204
 
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  is_suppressed,
18
18
  )
19
+ from sarj_python_lint.rules._ast_names import terminal_name as _terminal_name
19
20
  from sarj_python_lint.rules._prose_budget import groups
20
21
 
21
22
 
@@ -138,11 +139,3 @@ def _is_public_contract(node: ast.FunctionDef | ast.AsyncFunctionDef, parents: M
138
139
  _terminal_name(base) in _PUBLIC_CONTRACT_BASES for base in owner.bases
139
140
  )
140
141
  return not decorators.isdisjoint(_PUBLIC_CONTRACT_DECORATORS) or protocol_method
141
-
142
-
143
- def _terminal_name(node: ast.expr) -> str | None:
144
- match node:
145
- case ast.Name(id=name) | ast.Attribute(attr=name):
146
- return name
147
- case _:
148
- return None
@@ -20,6 +20,7 @@ from sarj_python_lint.rule_base import (
20
20
  Severity,
21
21
  )
22
22
  from sarj_python_lint.rules._ast_index import object_list, walk as walk_ast
23
+ from sarj_python_lint.rules._ast_names import expression_root as _root_name
23
24
  from sarj_python_lint.rules._logging import LOG_METHODS, is_logger_expr
24
25
 
25
26
 
@@ -382,12 +383,6 @@ def _mutated_names(statement: ast.stmt) -> frozenset[str]:
382
383
  return frozenset(names)
383
384
 
384
385
 
385
- def _root_name(node: ast.expr) -> str | None:
386
- while isinstance(node, (ast.Attribute, ast.Subscript)):
387
- node = node.value
388
- return node.id if isinstance(node, ast.Name) else None
389
-
390
-
391
386
  def _containing_statement(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> ast.stmt | None:
392
387
  current: ast.AST | None = node
393
388
  while current is not None and not isinstance(current, ast.stmt):
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  RuleExample,
17
17
  Severity,
18
18
  )
19
+ from sarj_python_lint.rules._ast_names import dotted_name as _dotted
19
20
  from sarj_python_lint.rules._paths import is_test_path
20
21
 
21
22
 
@@ -809,18 +810,6 @@ def _joined_text(node: ast.JoinedStr) -> str:
809
810
  return "".join(parts)
810
811
 
811
812
 
812
- def _dotted(node: ast.expr) -> str | None:
813
- parts: list[str] = []
814
- current = node
815
- while isinstance(current, ast.Attribute):
816
- parts.append(current.attr)
817
- current = current.value
818
- if not isinstance(current, ast.Name):
819
- return None
820
- parts.append(current.id)
821
- return ".".join(reversed(parts))
822
-
823
-
824
813
  def _collect_parametrized_names(argnames: ast.expr | None, parametrized: set[str]) -> None:
825
814
  if isinstance(argnames, ast.Constant) and isinstance(argnames.value, str):
826
815
  parametrized.update(stripped_name for name in argnames.value.split(",") if (stripped_name := name.strip()))
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
16
16
  Severity,
17
17
  )
18
18
  from sarj_python_lint.rules._ast_index import walk as walk_ast
19
+ from sarj_python_lint.rules._ast_names import expression_root as _target_root
19
20
  from sarj_python_lint.rules._paths import is_test_path
20
21
 
21
22
 
@@ -466,12 +467,6 @@ def _writes_name(node: ast.stmt, name: str) -> bool:
466
467
  return any(_target_root(target) == name for target in targets)
467
468
 
468
469
 
469
- def _target_root(node: ast.expr) -> str | None:
470
- while isinstance(node, (ast.Attribute, ast.Subscript)):
471
- node = node.value
472
- return node.id if isinstance(node, ast.Name) else None
473
-
474
-
475
470
  def _dotted(node: ast.expr) -> str | None:
476
471
  parts: list[str] = []
477
472
  while isinstance(node, ast.Attribute):
@@ -4,10 +4,8 @@ import ast
4
4
  from collections import defaultdict
5
5
  from dataclasses import dataclass
6
6
  from enum import StrEnum
7
- from io import StringIO
8
7
  from itertools import pairwise
9
8
  from pathlib import PurePosixPath
10
- import tokenize
11
9
  from typing import TYPE_CHECKING, ClassVar, final, override
12
10
 
13
11
  from sarj_python_lint.rule_base import (
@@ -23,6 +21,7 @@ from sarj_python_lint.rule_base import (
23
21
  is_suppressed,
24
22
  )
25
23
  from sarj_python_lint.rules._ast_index import children, walk as walk_ast
24
+ from sarj_python_lint.rules._comments import comment_line_numbers
26
25
 
27
26
 
28
27
  if TYPE_CHECKING:
@@ -243,7 +242,6 @@ class PreferCollectionComprehension(Rule):
243
242
  @override
244
243
  def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
245
244
  path = context.path
246
- source = context.source
247
245
  if context.generated:
248
246
  return []
249
247
  tree = context.tree
@@ -251,7 +249,7 @@ class PreferCollectionComprehension(Rule):
251
249
  return []
252
250
 
253
251
  source_lines = context.source_lines
254
- comments = _comment_lines(source)
252
+ comments = comment_line_numbers(context.source)
255
253
  diagnostics: list[Diagnostic] = []
256
254
  set_shadowed = _set_is_shadowed(tree, node_index=context.node_index)
257
255
  namespace_observers = _namespace_observer_calls(context)
@@ -834,14 +832,6 @@ def _replacement_fits(init: _Init, name: str, expression: str, *, allow_multilin
834
832
  )
835
833
 
836
834
 
837
- def _comment_lines(source: str) -> frozenset[int]:
838
- try:
839
- tokens = tokenize.generate_tokens(StringIO(source).readline)
840
- return frozenset(token.start[0] for token in tokens if token.type == tokenize.COMMENT)
841
- except IndentationError, tokenize.TokenError:
842
- return frozenset()
843
-
844
-
845
835
  def _has_comment(init: _Init, loop: ast.For, comments: frozenset[int]) -> bool:
846
836
  end_line = loop.end_lineno or loop.lineno
847
837
  return any(init.lineno <= line <= end_line for line in comments)