sarj-python-lint 1.48.0__tar.gz → 1.50.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 (223) hide show
  1. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/.gitignore +12 -1
  2. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/PKG-INFO +1 -1
  3. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/pyproject.toml +1 -1
  4. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_registry.py +4 -0
  5. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_resource_provenance.py +126 -0
  6. sarj_python_lint-1.50.0/src/sarj_python_lint/rules/_runtime_bindings.py +113 -0
  7. sarj_python_lint-1.50.0/src/sarj_python_lint/rules/no_discarded_validation_result.py +732 -0
  8. sarj_python_lint-1.50.0/src/sarj_python_lint/rules/no_mutating_container_transform.py +472 -0
  9. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/LICENSE +0 -0
  10. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/README.md +0 -0
  11. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/__init__.py +0 -0
  12. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/__main__.py +0 -0
  13. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  14. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_file_context.py +0 -0
  15. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_filesystem.py +0 -0
  16. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_source.py +0 -0
  20. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/_version.py +0 -0
  21. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/benchmark.py +0 -0
  22. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  23. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/json_boundary.py +0 -0
  24. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/py.typed +0 -0
  25. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/ratchet.py +0 -0
  26. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rule_base.py +0 -0
  27. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  28. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  29. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  30. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  31. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  32. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  33. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  34. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  35. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  36. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  37. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  38. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  39. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  40. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  41. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  42. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  43. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  44. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  45. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
  46. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  47. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  48. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  49. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  50. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  51. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  52. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  53. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  54. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  55. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  56. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  57. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  58. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  59. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  60. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  61. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  62. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  63. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  64. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  65. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  66. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  67. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  68. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  69. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  70. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  71. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  72. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  73. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  74. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  75. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  76. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  77. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  78. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  79. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  80. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  81. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  82. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  83. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  84. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  85. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  86. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  87. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  88. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  89. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  90. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  91. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  92. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  93. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  94. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  95. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  96. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  97. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  98. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  99. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  100. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  101. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  102. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  103. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  104. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  105. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  106. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  107. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  108. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  109. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  110. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  111. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  112. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  113. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  114. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  115. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  116. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  117. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  118. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  119. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  120. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  121. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  122. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  123. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  124. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  125. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  126. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  127. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  128. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  129. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  130. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  131. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  132. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  133. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  134. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  135. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  136. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  137. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  138. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  139. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  140. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  141. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  142. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  143. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  144. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  145. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  146. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  147. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  148. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  149. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  150. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  151. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  152. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  153. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  154. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  155. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  156. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  157. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  158. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  159. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  160. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  161. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  162. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  163. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  164. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  165. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  166. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  167. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  168. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  169. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  170. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  171. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  172. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  173. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  174. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  175. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  176. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  177. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  178. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  179. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  180. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  181. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  182. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  183. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  184. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  185. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  186. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  187. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  188. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  189. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  190. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  191. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  192. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  193. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  194. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  195. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  196. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  197. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  198. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  199. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  200. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  201. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  202. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  203. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  204. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  205. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  206. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  207. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  208. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  209. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  210. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  211. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  212. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  213. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  214. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  215. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  216. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  217. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  218. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  219. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  220. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  221. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  222. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  223. {sarj_python_lint-1.48.0 → sarj_python_lint-1.50.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -32,7 +32,18 @@ Thumbs.db
32
32
  .zed/
33
33
  *.swp
34
34
 
35
- .claude/
35
+ .claude/*
36
+ !.claude/skills/
37
+ .claude/skills/*
38
+ !.claude/skills/audit-protocol/
39
+ .claude/skills/audit-protocol/*
40
+ !.claude/skills/audit-protocol/SKILL.md
41
+ !.claude/skills/lint-rule-generator/
42
+ .claude/skills/lint-rule-generator/*
43
+ !.claude/skills/lint-rule-generator/SKILL.md
44
+ !.claude/skills/test-value/
45
+ .claude/skills/test-value/*
46
+ !.claude/skills/test-value/SKILL.md
36
47
  apps/docs/.wrangler/
37
48
  apps/docs/.dev.vars*
38
49
  !apps/docs/.dev.vars.example
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.48.0
3
+ Version: 1.50.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.48.0"
3
+ version = "1.50.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" }]
@@ -43,6 +43,7 @@ from sarj_python_lint.rules.no_cors_wildcard_with_credentials import (
43
43
  )
44
44
  from sarj_python_lint.rules.no_database_programmability import NoDatabaseProgrammability
45
45
  from sarj_python_lint.rules.no_delete_statement import NoDeleteStatement
46
+ from sarj_python_lint.rules.no_discarded_validation_result import NoDiscardedValidationResult
46
47
  from sarj_python_lint.rules.no_dunder_all import NoDunderAll
47
48
  from sarj_python_lint.rules.no_duplicate_dunder_all_entry import NoDuplicateDunderAllEntry
48
49
  from sarj_python_lint.rules.no_excessive_cognitive_complexity import NoExcessiveCognitiveComplexity
@@ -69,6 +70,7 @@ from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvali
69
70
  from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
70
71
  from sarj_python_lint.rules.no_loguru_printf_format import NoLoguruPrintfFormat
71
72
  from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
73
+ from sarj_python_lint.rules.no_mutating_container_transform import NoMutatingContainerTransform
72
74
  from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
73
75
  from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
74
76
  from sarj_python_lint.rules.no_offset_pagination import NoOffsetPagination
@@ -249,6 +251,8 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
249
251
  NoFixedSleepInLoop.id: NoFixedSleepInLoop,
250
252
  NoInterpreterSourceArguments.id: NoInterpreterSourceArguments,
251
253
  NoUnusedCopyResult.id: NoUnusedCopyResult,
254
+ NoDiscardedValidationResult.id: NoDiscardedValidationResult,
255
+ NoMutatingContainerTransform.id: NoMutatingContainerTransform,
252
256
  NoBeforeValidatorInputMutation.id: NoBeforeValidatorInputMutation,
253
257
  NoInputModelMutation.id: NoInputModelMutation,
254
258
  NoDatabaseProgrammability.id: NoDatabaseProgrammability,
@@ -8,9 +8,12 @@ from typing import TYPE_CHECKING, final
8
8
 
9
9
  from sarj_python_lint.rules._ast_index import has_own_yield
10
10
  from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
11
+ from sarj_python_lint.rules._runtime_bindings import RuntimeBindings
11
12
 
12
13
 
13
14
  if TYPE_CHECKING:
15
+ from pathlib import Path
16
+
14
17
  from sarj_python_lint._file_context import PythonFileContext
15
18
 
16
19
 
@@ -76,6 +79,7 @@ class ResourceProvenance:
76
79
  self.escape_cache: dict[tuple[ast.AST, bool], bool] = {}
77
80
  self.async_contract_cache: dict[ast.AsyncFunctionDef, bool] = {}
78
81
  self.import_contract_cache: dict[str, bool] = {}
82
+ self.runtime_import_cache: dict[ast.alias, bool] = {}
79
83
  self.wildcard = False
80
84
  self.mutated: set[str] = set()
81
85
  self.external_bindings: set[str] = set()
@@ -133,6 +137,113 @@ class ResourceProvenance:
133
137
  scope = self.scope(scope)
134
138
  return None
135
139
 
140
+ @cached_property
141
+ def runtime_bindings(self) -> RuntimeBindings:
142
+ return RuntimeBindings(self.context, self.bindings)
143
+
144
+ def runtime_binding(self, name: str, at: ast.AST) -> ast.AST | None:
145
+ nodes = self.runtime_bindings.lookup(self.scope(at), name)
146
+ if not nodes:
147
+ return None
148
+ return nodes[0] if len(nodes) == 1 or self._same_import_bindings(list(nodes)) else None
149
+
150
+ def runtime_import_name(self, node: ast.expr) -> str | None:
151
+ root = _root_name(node)
152
+ if self.wildcard or self.dynamic_namespace or root is None or root in self.mutated:
153
+ return None
154
+ binding = self.runtime_binding(root, node)
155
+ if not isinstance(binding, ast.alias):
156
+ return None
157
+ qualified = import_name_for_binding(node, binding, self.context.parents.get(binding))
158
+ if qualified is None or not self._owner_namespace_untouched(qualified.partition(".")[0]):
159
+ return None
160
+ if binding not in self.runtime_import_cache:
161
+ self.runtime_import_cache[binding] = all(
162
+ self.runtime_binding(root, read) is not binding or self._safe_import_use(read)
163
+ for read in self.reads.get(root, ())
164
+ )
165
+ return qualified if self.runtime_import_cache[binding] else None
166
+
167
+ @cached_property
168
+ def mock_namespace_patching(self) -> bool:
169
+ return any(
170
+ self.runtime_import_name(node.func)
171
+ in {
172
+ "unittest.mock.patch",
173
+ "unittest.mock.patch.object",
174
+ "unittest.mock.patch.multiple",
175
+ "unittest.mock.patch.dict",
176
+ }
177
+ for node in self.context.nodes(ast.Call)
178
+ )
179
+
180
+ @cached_property
181
+ def imported_namespace_hazards(self) -> frozenset[Path]:
182
+ hazards: set[Path] = set()
183
+ for node in self.context.nodes(ast.Name, ast.Attribute):
184
+ match node:
185
+ case ast.Attribute(value=value, ctx=ast.Store() | ast.Del()):
186
+ hazards.update(self._namespace_paths(value, include_root=True))
187
+ case ast.Name(ctx=ast.Load()) | ast.Attribute(ctx=ast.Load()) if not self._safe_import_use(node):
188
+ hazards.update(self._namespace_paths(node, include_root=False))
189
+ case _:
190
+ pass
191
+ return frozenset(hazards)
192
+
193
+ @cached_property
194
+ def _namespace_aliases(self) -> dict[str, tuple[ast.alias, ...]]:
195
+ aliases: dict[str, list[ast.alias]] = defaultdict(list)
196
+ for binding in self.context.nodes(ast.alias):
197
+ parent = self.context.parents.get(binding)
198
+ name = binding.asname or (
199
+ binding.name.partition(".")[0] if isinstance(parent, ast.Import) else binding.name
200
+ )
201
+ aliases[name].append(binding)
202
+ return {name: tuple(bindings) for name, bindings in aliases.items()}
203
+
204
+ def _namespace_paths(self, expression: ast.expr, *, include_root: bool) -> set[Path]:
205
+ attributes: list[str] = []
206
+ root = expression
207
+ while isinstance(root, ast.Attribute):
208
+ attributes.append(root.attr)
209
+ root = root.value
210
+ if not isinstance(root, ast.Name):
211
+ return set()
212
+ binding = self.runtime_binding(root.id, expression)
213
+ aliases: tuple[ast.alias, ...]
214
+ match binding:
215
+ case ast.alias():
216
+ aliases = (binding,)
217
+ case None:
218
+ aliases = self._namespace_aliases.get(root.id, ())
219
+ case _:
220
+ return set()
221
+ paths: set[Path] = set()
222
+ for alias in aliases:
223
+ path = self._namespace_module_path(alias, tuple(reversed(attributes)))
224
+ if path is not None:
225
+ paths.add(path)
226
+ if include_root and attributes:
227
+ base = self._namespace_module_path(alias, ())
228
+ if base is not None:
229
+ paths.add(base)
230
+ return paths
231
+
232
+ def _namespace_module_path(self, binding: ast.alias, attributes: tuple[str, ...]) -> Path | None:
233
+ parent = self.context.parents.get(binding)
234
+ match parent:
235
+ case ast.Import():
236
+ prefix = binding.name if binding.asname else binding.name.partition(".")[0]
237
+ level = 0
238
+ case ast.ImportFrom(module=module, level=level):
239
+ prefix = ".".join(part for part in (module, binding.name) if part)
240
+ case _:
241
+ return None
242
+ module = ".".join((prefix, *attributes))
243
+ return self.context.session.local_source.resolve_module(
244
+ self.context.path, module, level, facts=self.context.session.first_party
245
+ )
246
+
136
247
  def _same_import_bindings(self, nodes: list[ast.AST]) -> bool:
137
248
  targets: set[tuple[str, str | None, int, str]] = set()
138
249
  for node in nodes:
@@ -417,6 +528,21 @@ def _root_name(node: ast.expr) -> str | None:
417
528
  return node.id if isinstance(node, ast.Name) else None
418
529
 
419
530
 
531
+ def import_name_for_binding(expression: ast.expr, binding: ast.alias, parent: ast.AST | None) -> str | None:
532
+ attributes: list[str] = []
533
+ while isinstance(expression, ast.Attribute):
534
+ attributes.append(expression.attr)
535
+ expression = expression.value
536
+ match parent:
537
+ case ast.Import():
538
+ prefix = binding.name if binding.asname else binding.name.partition(".")[0]
539
+ case ast.ImportFrom(module=str(module), level=0):
540
+ prefix = f"{module}.{binding.name}"
541
+ case _:
542
+ return None
543
+ return ".".join((prefix, *reversed(attributes)))
544
+
545
+
420
546
  def _bound_names(node: ast.AST) -> tuple[str, ...]:
421
547
  match node:
422
548
  case ast.Name(id=name, ctx=ast.Store() | ast.Del()) | ast.arg(arg=name):
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from collections import defaultdict
5
+ import symtable
6
+ from typing import TYPE_CHECKING, final
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Mapping, Sequence
11
+
12
+ from sarj_python_lint._file_context import PythonFileContext
13
+
14
+
15
+ @final
16
+ class RuntimeBindings:
17
+ def __init__(
18
+ self, context: PythonFileContext, bindings: Mapping[ast.AST | None, Mapping[str, Sequence[ast.AST]]]
19
+ ) -> None:
20
+ self.bindings = bindings
21
+ self.tables: dict[int, symtable.SymbolTable] = {}
22
+ self.parents: dict[int, int | None] = {}
23
+ self.by_scope: dict[ast.AST, int] = {}
24
+ self.scopes: dict[int, ast.AST] = {}
25
+ self.mutable: set[tuple[int, str]] = set()
26
+ self.module: int | None = None
27
+ tree = context.tree
28
+ if tree is None:
29
+ return
30
+ try:
31
+ root = symtable.symtable(context.source, str(context.path), "exec")
32
+ except SyntaxError, UnicodeError, RecursionError:
33
+ return
34
+ self.module = root.get_id()
35
+ self._collect(root)
36
+ self._map_scopes(context, tree, root)
37
+ self._index_writes()
38
+
39
+ def lookup(self, scope: ast.AST | None, name: str) -> Sequence[ast.AST] | None:
40
+ if scope is None or (table_id := self.by_scope.get(scope)) is None:
41
+ return None
42
+ symbol = _symbol(self.tables[table_id], name)
43
+ if symbol is None or (owner := self._owner(table_id, symbol)) is None or (owner, name) in self.mutable:
44
+ return None
45
+ owner_scope = self.scopes.get(owner)
46
+ return self.bindings.get(owner_scope, {}).get(name) if owner_scope is not None else None
47
+
48
+ def _collect(self, root: symtable.SymbolTable) -> None:
49
+ pending: list[tuple[symtable.SymbolTable, int | None]] = [(root, None)]
50
+ while pending:
51
+ table, parent = pending.pop()
52
+ identity = table.get_id()
53
+ self.tables[identity] = table
54
+ self.parents[identity] = parent
55
+ pending.extend((child, identity) for child in table.get_children())
56
+
57
+ def _map_scopes(self, context: PythonFileContext, tree: ast.Module, root: symtable.SymbolTable) -> None:
58
+ candidates: dict[tuple[symtable.SymbolTableType, str, int], list[int]] = defaultdict(list)
59
+ for identity, table in self.tables.items():
60
+ candidates[table.get_type(), table.get_name(), table.get_lineno()].append(identity)
61
+ self.by_scope[tree] = root.get_id()
62
+ self.scopes[root.get_id()] = tree
63
+ for node in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef):
64
+ kind = (
65
+ symtable.SymbolTableType.CLASS if isinstance(node, ast.ClassDef) else symtable.SymbolTableType.FUNCTION
66
+ )
67
+ matches = candidates.get((kind, node.name, node.lineno), ())
68
+ if len(matches) == 1:
69
+ [identity] = matches
70
+ self.by_scope[node] = identity
71
+ self.scopes[identity] = node
72
+
73
+ def _index_writes(self) -> None:
74
+ for identity, table in self.tables.items():
75
+ if identity == self.module:
76
+ continue
77
+ for symbol in table.get_symbols():
78
+ if not (symbol.is_global() or symbol.is_nonlocal()):
79
+ continue
80
+ if not (symbol.is_assigned() or symbol.is_imported() or symbol.is_namespace()):
81
+ continue
82
+ owner = self._owner(identity, symbol)
83
+ if owner is not None:
84
+ self.mutable.add((owner, symbol.get_name()))
85
+
86
+ def _owner(self, identity: int, symbol: symtable.Symbol) -> int | None:
87
+ if symbol.is_global():
88
+ return self.module
89
+ if symbol.is_local():
90
+ return identity
91
+ if not symbol.is_free():
92
+ return None
93
+ parent = self.parents.get(identity)
94
+ while parent is not None:
95
+ table = self.tables[parent]
96
+ if table.get_type() is symtable.SymbolTableType.MODULE:
97
+ break
98
+ if table.get_type() is symtable.SymbolTableType.CLASS and symbol.get_name() == "__class__":
99
+ # The compiler binds this cell to the lexically defined class.
100
+ return None
101
+ if table.get_type() is not symtable.SymbolTableType.CLASS:
102
+ ancestor = _symbol(table, symbol.get_name())
103
+ if ancestor is not None and ancestor.is_local():
104
+ return parent
105
+ parent = self.parents.get(parent)
106
+ return None
107
+
108
+
109
+ def _symbol(table: symtable.SymbolTable, name: str) -> symtable.Symbol | None:
110
+ try:
111
+ return table.lookup(name)
112
+ except KeyError:
113
+ return None