sarj-python-lint 1.37.0__tar.gz → 1.39.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 (205) hide show
  1. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_registry.py +4 -0
  4. sarj_python_lint-1.39.0/src/sarj_python_lint/rules/no_async_function_cache.py +179 -0
  5. sarj_python_lint-1.39.0/src/sarj_python_lint/rules/no_loguru_exc_info.py +178 -0
  6. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/.gitignore +0 -0
  7. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/LICENSE +0 -0
  8. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/README.md +0 -0
  9. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_source.py +0 -0
  18. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_version.py +0 -0
  19. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/benchmark.py +0 -0
  20. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  21. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/json_boundary.py +0 -0
  22. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/py.typed +0 -0
  23. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/ratchet.py +0 -0
  24. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rule_base.py +0 -0
  25. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  26. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  27. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  28. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  29. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  30. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  31. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  32. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  33. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  34. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  35. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  36. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  37. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  38. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  39. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  40. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  41. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  42. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  43. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  44. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  45. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  46. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  47. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  48. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  49. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  50. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  51. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  52. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  53. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  54. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  55. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  56. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  57. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  58. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  59. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  60. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  61. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  62. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  63. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  64. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  65. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  66. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  67. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  68. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  69. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  70. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  71. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  72. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  73. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  74. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  75. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  76. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  77. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  78. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  79. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  80. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  81. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  82. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  83. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  84. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  85. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  86. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  87. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  88. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  89. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  90. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  91. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  92. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  93. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  94. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  95. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  96. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  97. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  98. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  99. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  100. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  101. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  102. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  103. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  104. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  105. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  106. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  107. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  108. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  109. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  110. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  111. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  112. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  113. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  114. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  115. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  116. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  117. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  118. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  119. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  120. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  121. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  122. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  123. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  124. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  125. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  126. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  127. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  128. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  129. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  130. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  131. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  132. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  133. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  134. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  135. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  136. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  137. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  138. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  139. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  140. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  141. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  142. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  143. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  144. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  145. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  146. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  147. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  148. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  149. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  150. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  151. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  152. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  153. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  154. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  155. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  156. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  157. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  158. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  159. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  160. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  161. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  162. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  163. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  164. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  165. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  166. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  167. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  168. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  169. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  170. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  171. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  172. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  173. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  174. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  175. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  176. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  177. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  178. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  179. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  180. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  181. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  182. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  183. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  184. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  185. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  186. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  187. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  188. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  189. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  190. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  191. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  192. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  193. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  194. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  195. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  196. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  197. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  198. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  199. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  200. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  201. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  202. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  203. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  204. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  205. {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.37.0
3
+ Version: 1.39.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.37.0"
3
+ version = "1.39.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" }]
@@ -32,6 +32,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import (
32
32
  NoAnalyticalAggregationInPostgresStore,
33
33
  )
34
34
  from sarj_python_lint.rules.no_any_mapping_types import NoAnyMappingTypes
35
+ from sarj_python_lint.rules.no_async_function_cache import NoAsyncFunctionCache
35
36
  from sarj_python_lint.rules.no_before_validator_input_mutation import NoBeforeValidatorInputMutation
36
37
  from sarj_python_lint.rules.no_broad_keyword_capture import NoBroadKeywordCapture
37
38
  from sarj_python_lint.rules.no_comment_cruft import NoCommentCruft
@@ -65,6 +66,7 @@ from sarj_python_lint.rules.no_injected_module_loader import NoInjectedModuleLoa
65
66
  from sarj_python_lint.rules.no_input_model_mutation import NoInputModelMutation
66
67
  from sarj_python_lint.rules.no_interpreter_source_arguments import NoInterpreterSourceArguments
67
68
  from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvalidArgumentNameSuppression
69
+ from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
68
70
  from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
69
71
  from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
70
72
  from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
@@ -241,6 +243,8 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
241
243
  NoSwallowedAsyncioCancellation.id: NoSwallowedAsyncioCancellation,
242
244
  RequireExplicitPsycopgTransaction.id: RequireExplicitPsycopgTransaction,
243
245
  AsyncMockCallWithoutAwaitAssertion.id: AsyncMockCallWithoutAwaitAssertion,
246
+ NoAsyncFunctionCache.id: NoAsyncFunctionCache,
247
+ NoLoguruExcInfo.id: NoLoguruExcInfo,
244
248
  ComplexPostgresQueryRequiresArchitectureReview.id: ComplexPostgresQueryRequiresArchitectureReview,
245
249
  ExcessiveCommentary.id: ExcessiveCommentary,
246
250
  PreferWalrusAwaitedNoneGuard.id: PreferWalrusAwaitedNoneGuard,
@@ -0,0 +1,179 @@
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._ast_index import children
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+ from sarj_python_lint.rules._imports import ImportIndex
25
+
26
+
27
+ _FUNCTOOLS = frozenset({"functools"})
28
+ _COROUTINE_CACHES = frozenset({"cache", "cached_property", "lru_cache"})
29
+ _LRU_CACHE_POSITIONAL_ARGUMENTS = 2
30
+
31
+
32
+ @final
33
+ class NoAsyncFunctionCache(Rule):
34
+ id = "no-async-function-cache"
35
+ code = "SARJ507"
36
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
37
+ default_level=Severity.WARNING,
38
+ summary="Standard library cache decorates a coroutine function and retains single-use coroutine objects.",
39
+ rationale=(
40
+ "`functools.cache`, `lru_cache`, and `cached_property` store the coroutine object returned by an async "
41
+ "function rather than its awaited value. A subsequent cache hit can raise `RuntimeError: cannot reuse "
42
+ "already awaited coroutine`."
43
+ ),
44
+ remediation="Remove the standard library cache or use an async-aware cache that stores completed results.",
45
+ category=RuleCategory.CORRECTNESS,
46
+ autofix=AutofixPolicy.NONE,
47
+ limitations=(
48
+ "Only a proven functools import used as the innermost decorator on a coroutine function is reported.",
49
+ "Async generators, transformed functions, ambiguous or rebound imports, and generated files are excluded.",
50
+ "Wildcard imports and names rebound by relative imports or imports outside the module body are excluded conservatively.",
51
+ "lru_cache with disabled, dynamic, or unpacked maxsize arguments is excluded; an omitted size or literal positive integer or None is required.",
52
+ "Indirect wrapping calls and runtime monkeypatches are not resolved. Test code is checked because coroutine reuse fails there too.",
53
+ "No automatic rewrite is offered because cache lifetime, cancellation, and concurrency require an explicit design decision.",
54
+ ),
55
+ examples=(
56
+ RuleExample(
57
+ example_id="cached-coroutine-function",
58
+ title="A standard cache retains an exhausted coroutine",
59
+ outcome=ExampleOutcome.MATCH,
60
+ files=(
61
+ ExampleFile.python(
62
+ "app/cache.py",
63
+ "from functools import cache\n\n@cache\nasync def load() -> int:\n return 1\n",
64
+ ),
65
+ ),
66
+ focus_path=PurePosixPath("app/cache.py"),
67
+ expected_count=1,
68
+ public=True,
69
+ ),
70
+ RuleExample(
71
+ example_id="fresh-coroutine-function",
72
+ title="Each call creates a fresh coroutine",
73
+ outcome=ExampleOutcome.NO_MATCH,
74
+ files=(ExampleFile.python("app/cache.py", "async def load() -> int:\n return 1\n"),),
75
+ focus_path=PurePosixPath("app/cache.py"),
76
+ expected_count=0,
77
+ public=True,
78
+ ),
79
+ ),
80
+ )
81
+ description = documentation.summary
82
+
83
+ @override
84
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
85
+ if "async" not in context.source or context.generated or context.tree is None:
86
+ return []
87
+ shadowed = _nested_import_names(context)
88
+ if "*" in shadowed:
89
+ return []
90
+ diagnostics: list[Diagnostic] = []
91
+ for function in context.nodes(ast.AsyncFunctionDef):
92
+ if not function.decorator_list:
93
+ continue
94
+ decorator = function.decorator_list[-1]
95
+ if (
96
+ not _is_coroutine_cache(decorator, context.imports, shadowed)
97
+ or _has_own_yield(function)
98
+ or is_suppressed(context.source_lines, decorator.lineno, self.code)
99
+ ):
100
+ continue
101
+ diagnostics.append(
102
+ Diagnostic(
103
+ path=context.path,
104
+ line=decorator.lineno,
105
+ col=decorator.col_offset + 1,
106
+ code=self.code,
107
+ message=(
108
+ "Standard library cache retains a single-use coroutine; remove it or use an async-aware "
109
+ "cache that stores awaited results."
110
+ ),
111
+ severity=Severity.WARNING,
112
+ ),
113
+ )
114
+ return diagnostics
115
+
116
+
117
+ def _nested_import_names(context: PythonFileContext) -> frozenset[str]:
118
+ return frozenset(
119
+ alias.asname or alias.name.partition(".")[0]
120
+ for node in context.nodes(ast.Import, ast.ImportFrom)
121
+ for alias in node.names
122
+ if context.parents[node] is not context.tree
123
+ or (isinstance(node, ast.ImportFrom) and node.level > 0)
124
+ or alias.name == "*"
125
+ )
126
+
127
+
128
+ def _is_coroutine_cache(decorator: ast.expr, imports: ImportIndex, shadowed: frozenset[str]) -> bool:
129
+ target = decorator.func if isinstance(decorator, ast.Call) else decorator
130
+ root = target
131
+ while isinstance(root, ast.Attribute):
132
+ root = root.value
133
+ if isinstance(root, ast.Name) and root.id in shadowed:
134
+ return False
135
+ symbol = imports.resolved_symbol(target, sources=_FUNCTOOLS)
136
+ if symbol not in _COROUTINE_CACHES:
137
+ return False
138
+ if not isinstance(decorator, ast.Call):
139
+ return True
140
+ if symbol != "lru_cache" or any(keyword.arg is None for keyword in decorator.keywords):
141
+ return False
142
+ if len(decorator.args) > _LRU_CACHE_POSITIONAL_ARGUMENTS or any(
143
+ isinstance(argument, ast.Starred) for argument in decorator.args
144
+ ):
145
+ return False
146
+ sizes = [keyword.value for keyword in decorator.keywords if keyword.arg == "maxsize"]
147
+ if decorator.args:
148
+ sizes.append(decorator.args[0])
149
+ if not sizes:
150
+ return True
151
+ if len(sizes) != 1:
152
+ return False
153
+ match sizes[0]:
154
+ case ast.Constant(value=None):
155
+ return True
156
+ case ast.Constant(value=int() as size) | ast.UnaryOp(op=ast.UAdd(), operand=ast.Constant(value=int() as size)):
157
+ return size > 0
158
+ case _:
159
+ return False
160
+
161
+
162
+ def _has_own_yield(function: ast.AsyncFunctionDef) -> bool:
163
+ pending: list[ast.AST] = list(function.body)
164
+ while pending:
165
+ match pending.pop():
166
+ case ast.Yield() | ast.YieldFrom():
167
+ return True
168
+ case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.Lambda() as definition:
169
+ pending.extend(definition.args.defaults)
170
+ pending.extend(default for default in definition.args.kw_defaults if default is not None)
171
+ if not isinstance(definition, ast.Lambda):
172
+ pending.extend(definition.decorator_list)
173
+ case ast.ClassDef() as definition:
174
+ pending.extend(definition.bases)
175
+ pending.extend(keyword.value for keyword in definition.keywords)
176
+ pending.extend(definition.decorator_list)
177
+ case node:
178
+ pending.extend(children(node))
179
+ return False
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from string import Formatter
6
+ from typing import TYPE_CHECKING, ClassVar, final, override
7
+
8
+ from sarj_python_lint.rule_base import (
9
+ AutofixPolicy,
10
+ Diagnostic,
11
+ ExampleFile,
12
+ ExampleOutcome,
13
+ Rule,
14
+ RuleCategory,
15
+ RuleDocumentation,
16
+ RuleExample,
17
+ Severity,
18
+ is_suppressed,
19
+ )
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from sarj_python_lint._file_context import PythonFileContext
24
+
25
+
26
+ _METHODS = frozenset({"trace", "debug", "info", "success", "warning", "error", "critical", "log"})
27
+ _LOGURU = frozenset({"loguru"})
28
+
29
+
30
+ @final
31
+ class NoLoguruExcInfo(Rule):
32
+ id = "no-loguru-exc-info"
33
+ code = "SARJ508"
34
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
35
+ default_level=Severity.WARNING,
36
+ summary="Loguru exc_info=True adds a field rather than retaining the exception traceback.",
37
+ rationale=(
38
+ "Loguru accepts logging keyword arguments as structured fields. The standard logging `exc_info=True` "
39
+ "argument therefore does not attach an exception, leaving failure logs without their traceback."
40
+ ),
41
+ remediation="Use `logger.opt(exception=True)` or `logger.exception(...)` when reporting the active exception.",
42
+ category=RuleCategory.CORRECTNESS,
43
+ autofix=AutofixPolicy.NONE,
44
+ limitations=(
45
+ "Only unambiguous Loguru imports and immediate bind/opt chains with literal exc_info=True are checked.",
46
+ "A literal message is required; templates using exc_info or the record, explicit exception capture, and exception() are excluded.",
47
+ "Unknown receivers, stored logger aliases, dynamic exc_info values, ambiguous imports, and generated files are excluded.",
48
+ "Runtime monkeypatches of Loguru objects or methods are not resolved.",
49
+ "An intentionally stored field can use a diagnostic-specific suppression. No automatic rewrite changes logging context.",
50
+ ),
51
+ examples=(
52
+ RuleExample(
53
+ example_id="stdlib-argument-on-loguru",
54
+ title="A standard logging argument does not capture the exception",
55
+ outcome=ExampleOutcome.MATCH,
56
+ files=(
57
+ ExampleFile.python(
58
+ "app/service.py",
59
+ 'from loguru import logger\ntry:\n int("invalid")\nexcept ValueError:\n logger.warning("failed", exc_info=True)\n',
60
+ ),
61
+ ),
62
+ focus_path=PurePosixPath("app/service.py"),
63
+ expected_count=1,
64
+ public=True,
65
+ ),
66
+ RuleExample(
67
+ example_id="loguru-exception-option",
68
+ title="Loguru captures the exception through opt",
69
+ outcome=ExampleOutcome.NO_MATCH,
70
+ files=(
71
+ ExampleFile.python(
72
+ "app/service.py",
73
+ 'from loguru import logger\ntry:\n int("invalid")\nexcept ValueError:\n logger.opt(exception=True).warning("failed")\n',
74
+ ),
75
+ ),
76
+ focus_path=PurePosixPath("app/service.py"),
77
+ expected_count=0,
78
+ public=True,
79
+ ),
80
+ ),
81
+ )
82
+ description = documentation.summary
83
+
84
+ @override
85
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
86
+ if "exc_info" not in context.source or context.generated or context.tree is None:
87
+ return []
88
+ shadowed = _ambiguous_import_names(context)
89
+ if "*" in shadowed:
90
+ return []
91
+ diagnostics: list[Diagnostic] = []
92
+ for call in context.nodes(ast.Call):
93
+ if not isinstance(call.func, ast.Attribute) or call.func.attr not in _METHODS:
94
+ continue
95
+ keyword = next((item for item in call.keywords if item.arg == "exc_info"), None)
96
+ if keyword is None or not isinstance(keyword.value, ast.Constant) or keyword.value.value is not True:
97
+ continue
98
+ if (
99
+ not _is_unconfigured_loguru(call.func.value, context, shadowed)
100
+ or not _has_unused_exc_info(call)
101
+ or is_suppressed(context.source_lines, keyword.lineno, self.code)
102
+ ):
103
+ continue
104
+ diagnostics.append(
105
+ Diagnostic(
106
+ path=context.path,
107
+ line=keyword.lineno,
108
+ col=keyword.col_offset + 1,
109
+ code=self.code,
110
+ message="Loguru treats exc_info=True as a field; use opt(exception=True) or exception() to retain the traceback.",
111
+ severity=Severity.WARNING,
112
+ ),
113
+ )
114
+ return diagnostics
115
+
116
+
117
+ def _ambiguous_import_names(context: PythonFileContext) -> frozenset[str]:
118
+ return frozenset(
119
+ alias.asname or alias.name.partition(".")[0]
120
+ for node in context.nodes(ast.Import, ast.ImportFrom)
121
+ for alias in node.names
122
+ if context.parents[node] is not context.tree
123
+ or (isinstance(node, ast.ImportFrom) and node.level > 0)
124
+ or alias.name == "*"
125
+ )
126
+
127
+
128
+ def _is_unconfigured_loguru(receiver: ast.expr, context: PythonFileContext, shadowed: frozenset[str]) -> bool:
129
+ opted = False
130
+ while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
131
+ method = receiver.func.attr
132
+ if method not in {"bind", "opt"}:
133
+ return False
134
+ if method == "opt" and not opted:
135
+ opted = True
136
+ if any(
137
+ keyword.arg is None
138
+ or (
139
+ keyword.arg == "exception"
140
+ and not (isinstance(keyword.value, ast.Constant) and not keyword.value.value)
141
+ )
142
+ for keyword in receiver.keywords
143
+ ):
144
+ return False
145
+ receiver = receiver.func.value
146
+ root = receiver
147
+ while isinstance(root, ast.Attribute):
148
+ root = root.value
149
+ return not (isinstance(root, ast.Name) and root.id in shadowed) and context.imports.resolves(
150
+ receiver, sources=_LOGURU, symbol="logger"
151
+ )
152
+
153
+
154
+ def _has_unused_exc_info(call: ast.Call) -> bool:
155
+ message_index = int(isinstance(call.func, ast.Attribute) and call.func.attr == "log")
156
+ message = (
157
+ call.args[message_index]
158
+ if len(call.args) > message_index
159
+ else next((keyword.value for keyword in call.keywords if keyword.arg == "message"), None)
160
+ )
161
+ if not isinstance(message, ast.Constant) or not isinstance(message.value, str):
162
+ return False
163
+ return not _template_may_use_exc_info(message.value)
164
+
165
+
166
+ def _template_may_use_exc_info(template: str) -> bool:
167
+ pending = [template]
168
+ try:
169
+ while pending:
170
+ for _, field, spec, _ in Formatter().parse(pending.pop()):
171
+ if field is not None:
172
+ if field.partition(".")[0].partition("[")[0] in {"exc_info", "record"}:
173
+ return True
174
+ if spec:
175
+ pending.append(spec)
176
+ except ValueError:
177
+ return True
178
+ return False