sarj-python-lint 1.9.2__tar.gz → 1.10.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 (167) hide show
  1. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. sarj_python_lint-1.10.1/src/sarj_python_lint/rules/no_injected_module_loader.py +307 -0
  5. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/.gitignore +0 -0
  6. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/LICENSE +0 -0
  7. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/README.md +0 -0
  8. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/__init__.py +0 -0
  9. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/__main__.py +0 -0
  10. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  11. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_file_context.py +0 -0
  12. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_filesystem.py +0 -0
  13. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_python_target.py +0 -0
  14. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  15. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_secret_names.py +0 -0
  16. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/_version.py +0 -0
  17. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/benchmark.py +0 -0
  18. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/json_boundary.py +0 -0
  19. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/py.typed +0 -0
  20. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/ratchet.py +0 -0
  21. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rule_base.py +0 -0
  22. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  23. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  24. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  25. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  26. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  27. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  28. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  29. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  30. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  31. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  32. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  33. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  34. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  35. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  36. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  37. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  38. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  39. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  40. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  41. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  42. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  43. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  44. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  45. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  46. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  47. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  48. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  49. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  50. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
  51. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  52. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  53. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  54. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  55. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  56. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  57. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  58. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  59. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  60. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  61. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  62. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  63. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  64. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  65. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  66. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  67. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  68. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  69. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  70. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  71. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  72. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  73. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  74. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  75. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  76. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  77. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  78. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  79. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  80. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  81. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  82. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  83. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  84. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  85. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  86. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  87. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  88. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  89. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  90. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  91. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  92. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  93. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  94. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  95. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  96. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  97. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  98. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  99. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  100. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  101. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  102. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  103. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  104. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  105. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  106. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  107. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  108. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  109. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  110. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  111. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  112. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  113. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  114. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  115. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  116. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  117. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  118. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  119. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  120. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  121. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  122. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  123. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  124. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  125. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  126. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  127. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  128. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  129. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  130. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  131. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  132. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  133. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  134. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  135. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  136. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  137. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  138. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  139. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  140. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  141. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  142. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  143. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  144. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  145. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  146. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  147. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  148. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  149. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  150. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  151. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  152. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  153. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  154. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  155. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  156. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  157. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  158. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  159. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  160. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  161. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  162. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  163. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  164. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  165. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  166. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  167. {sarj_python_lint-1.9.2 → sarj_python_lint-1.10.1}/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.9.2
3
+ Version: 1.10.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.9.2"
3
+ version = "1.10.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" }]
@@ -59,6 +59,7 @@ from sarj_python_lint.rules.no_generic_single_export_module import NoGenericSing
59
59
  from sarj_python_lint.rules.no_hidden_constructor_fallback import (
60
60
  NoHiddenConstructorFallback,
61
61
  )
62
+ from sarj_python_lint.rules.no_injected_module_loader import NoInjectedModuleLoader
62
63
  from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvalidArgumentNameSuppression
63
64
  from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
64
65
  from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
@@ -257,6 +258,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
257
258
  NoRandomUuidInSql.id: NoRandomUuidInSql,
258
259
  NoRedundantModuleAliasExports.id: NoRedundantModuleAliasExports,
259
260
  NoHiddenConstructorFallback.id: NoHiddenConstructorFallback,
261
+ NoInjectedModuleLoader.id: NoInjectedModuleLoader,
260
262
  NoNullableDependencyFallback.id: NoNullableDependencyFallback,
261
263
  NoFileLevelEscapeHatchSuppression.id: NoFileLevelEscapeHatchSuppression,
262
264
  NoInvalidArgumentNameSuppression.id: NoInvalidArgumentNameSuppression,
@@ -0,0 +1,307 @@
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
+ )
18
+ from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
19
+ from sarj_python_lint.rules.no_hidden_constructor_fallback import LocalBindingCollector, scope_bindings
20
+
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterator, Mapping
24
+
25
+ from sarj_python_lint._file_context import PythonFileContext
26
+
27
+
28
+ _LOADER_TARGETS = frozenset({"importlib.import_module", "importlib.__import__", "builtins.__import__"})
29
+ _LEXICAL_SCOPES = (
30
+ ast.Module,
31
+ ast.FunctionDef,
32
+ ast.AsyncFunctionDef,
33
+ ast.ClassDef,
34
+ ast.Lambda,
35
+ ast.ListComp,
36
+ ast.SetComp,
37
+ ast.DictComp,
38
+ ast.GeneratorExp,
39
+ )
40
+
41
+
42
+ @final
43
+ class NoInjectedModuleLoader(Rule):
44
+ id = "no-injected-module-loader"
45
+ code = "SARJ471"
46
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
47
+ default_level=Severity.WARNING,
48
+ summary="Keep Python module loaders out of application parameter defaults.",
49
+ rationale="Exposing Python's import machinery as an injectable dependency expands the callable contract and hides the concrete imported API behind a generic loader.",
50
+ remediation="Use a normal or lazy import inside the implementation; pass the concrete configuration, object, or domain factory when callers need a real dependency boundary.",
51
+ category=RuleCategory.ARCHITECTURE,
52
+ autofix=AutofixPolicy.NONE,
53
+ limitations=(
54
+ "Flags defaults resolving to importlib.import_module, importlib.__import__, or builtins.__import__, including stable module-level import and single-assignment aliases and unshadowed __import__.",
55
+ "Tests, test support, and generated code are excluded. Required loader parameters, application loaders, wrapper lambdas, alias chains, comprehensions, and imports local to enclosing scopes are not inferred.",
56
+ "Conservatively skips shadowed or rebound names, wildcard-import files, reassigned loader attributes, and exception or pattern captures of importer roots, symbols or assignment aliases. A relevant-name capture in an unrelated local scope can cause a false negative because capture checks are file-wide.",
57
+ "Assignment aliases require one module binding and precede use by line and column. Defaults use their enclosing evaluation scope: earlier statements in the nearest executing class body can shadow loaders; methods and nested classes do not close over class attributes. Enclosing function body bindings remain conservative exclusions, but a function's parameters do not shadow its own defaults. Relevant global declarations and walrus writes exclude sensitive importer names. Compound class statements can cause conservative omissions. Dynamic namespace mutation is not inferred. No autofix: changing arguments or import placement can alter API and initialization behavior.",
58
+ ),
59
+ examples=(
60
+ RuleExample(
61
+ example_id="injected-loader",
62
+ title="Import machinery becomes a configurable parameter",
63
+ outcome=ExampleOutcome.MATCH,
64
+ files=(
65
+ ExampleFile.python(
66
+ "app/service.py",
67
+ "import importlib\ndef run(*, loader=importlib.import_module):\n return loader('plugin').create()\n",
68
+ ),
69
+ ),
70
+ focus_path=PurePosixPath("app/service.py"),
71
+ expected_count=1,
72
+ public=True,
73
+ ),
74
+ RuleExample(
75
+ example_id="lazy-import",
76
+ title="Implementation owns its lazy import",
77
+ outcome=ExampleOutcome.NO_MATCH,
78
+ files=(
79
+ ExampleFile.python(
80
+ "app/service.py", "def run():\n from plugin import create\n return create()\n"
81
+ ),
82
+ ),
83
+ focus_path=PurePosixPath("app/service.py"),
84
+ expected_count=0,
85
+ public=True,
86
+ ),
87
+ ),
88
+ )
89
+ description = documentation.summary
90
+
91
+ @override
92
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
93
+ if (
94
+ context.tree is None
95
+ or context.generated
96
+ or is_test_path(context.path)
97
+ or is_test_support_path(context.path)
98
+ ):
99
+ return []
100
+ aliases = _stable_loader_aliases(context)
101
+ if _ambiguous_loader_bindings(context, aliases):
102
+ return []
103
+ findings: list[Diagnostic] = []
104
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda):
105
+ for argument, default in _defaults(function.args):
106
+ direct = _is_loader(default, context)
107
+ aliased = (
108
+ isinstance(default, ast.Name)
109
+ and default.id in aliases
110
+ and (aliases[default.id].lineno, aliases[default.id].col_offset)
111
+ < (function.lineno, function.col_offset)
112
+ )
113
+ if not (direct or aliased) or _shadowed(default, function, context):
114
+ continue
115
+ findings.append(
116
+ Diagnostic(
117
+ path=context.path,
118
+ line=argument.lineno,
119
+ col=argument.col_offset + 1,
120
+ code=self.code,
121
+ message=f"Parameter `{argument.arg}` exposes Python's module loader; import directly or pass a concrete domain dependency.",
122
+ severity=Severity.WARNING,
123
+ )
124
+ )
125
+ return sorted(findings, key=lambda finding: (finding.line, finding.col))
126
+
127
+
128
+ def _ambiguous_loader_bindings(context: PythonFileContext, aliases: Mapping[str, ast.stmt]) -> bool:
129
+ if any(alias.name == "*" for node in context.nodes(ast.ImportFrom) for alias in node.names):
130
+ return True
131
+ sensitive_names = {"__import__", *aliases} | {
132
+ name
133
+ for name, target in context.module_imports.bindings.items()
134
+ if (target.module in {"importlib", "builtins"} and target.symbol is None)
135
+ or f"{target.module}.{target.symbol}" in _LOADER_TARGETS
136
+ }
137
+ if any(sensitive_names.intersection(node.names) for node in context.nodes(ast.Global)):
138
+ return True
139
+ for node in context.nodes(ast.ExceptHandler, ast.MatchAs, ast.MatchStar, ast.MatchMapping, ast.NamedExpr):
140
+ match node:
141
+ case (
142
+ ast.ExceptHandler(name=name)
143
+ | ast.MatchAs(name=name)
144
+ | ast.MatchStar(name=name)
145
+ | ast.MatchMapping(rest=name)
146
+ | ast.NamedExpr(target=ast.Name(id=name))
147
+ ):
148
+ if name in sensitive_names:
149
+ return True
150
+ return any(
151
+ isinstance(node.ctx, (ast.Store, ast.Del))
152
+ and context.module_imports.resolved_qualified_name(node) in _LOADER_TARGETS
153
+ for node in context.nodes(ast.Attribute)
154
+ )
155
+
156
+
157
+ def _defaults(arguments: ast.arguments) -> Iterator[tuple[ast.arg, ast.expr]]:
158
+ positional = (*arguments.posonlyargs, *arguments.args)
159
+ defaulted = positional[-len(arguments.defaults) :] if arguments.defaults else ()
160
+ yield from zip(defaulted, arguments.defaults, strict=True)
161
+ yield from (
162
+ (argument, default)
163
+ for argument, default in zip(arguments.kwonlyargs, arguments.kw_defaults, strict=True)
164
+ if default is not None
165
+ )
166
+
167
+
168
+ def _is_loader(default: ast.expr, context: PythonFileContext) -> bool:
169
+ if context.module_imports.resolved_qualified_name(default) in _LOADER_TARGETS:
170
+ return True
171
+ return (
172
+ isinstance(default, ast.Name)
173
+ and default.id == "__import__"
174
+ and context.module_imports.builtin_is_unshadowed("__import__")
175
+ )
176
+
177
+
178
+ def _shadowed(default: ast.expr, function: ast.AST, context: PythonFileContext) -> bool:
179
+ root = default
180
+ while isinstance(root, ast.Attribute):
181
+ root = root.value
182
+ if not isinstance(root, ast.Name):
183
+ return True
184
+ child = function
185
+ parent = context.parents.get(child)
186
+ class_scope_visible = True
187
+ while parent is not None:
188
+ if isinstance(parent, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
189
+ return True
190
+ if isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
191
+ if _in_scope_body(parent, child):
192
+ if root.id in scope_bindings(parent):
193
+ return True
194
+ class_scope_visible = False
195
+ elif isinstance(parent, ast.ClassDef) and _in_scope_body(parent, child):
196
+ if class_scope_visible and _class_prefix_shadows(parent, child, root.id):
197
+ return True
198
+ class_scope_visible = False
199
+ child = parent
200
+ parent = context.parents.get(parent)
201
+ return False
202
+
203
+
204
+ def _in_scope_body(scope: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.Lambda, child: ast.AST) -> bool:
205
+ # Defaults and class headers execute outside the scope being defined.
206
+ return scope.body is child if isinstance(scope, ast.Lambda) else child in scope.body
207
+
208
+
209
+ def _class_prefix_shadows(owner: ast.ClassDef, containing: ast.AST, name: str) -> bool:
210
+ collector = LocalBindingCollector()
211
+ for statement in owner.body:
212
+ if statement is containing:
213
+ match statement:
214
+ case ast.Assign(value=value) | ast.AnnAssign(value=ast.expr() as value):
215
+ collector.visit(value)
216
+ case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
217
+ pass
218
+ case _:
219
+ collector.visit(statement)
220
+ break
221
+ collector.visit(statement)
222
+ return name in collector.names
223
+
224
+
225
+ def _stable_loader_aliases(context: PythonFileContext) -> dict[str, ast.stmt]:
226
+ if context.tree is None:
227
+ return {}
228
+ aliases: dict[str, ast.stmt] = {}
229
+ for statement in context.tree.body:
230
+ match statement:
231
+ case (
232
+ ast.Assign(targets=[ast.Name(id=name)], value=value)
233
+ | ast.AnnAssign(target=ast.Name(id=name), value=ast.expr() as value)
234
+ ):
235
+ if (
236
+ _is_loader(value, context)
237
+ and _loader_import_precedes(value, statement, context)
238
+ and _binding_count(name, context) == 1
239
+ ):
240
+ aliases[name] = statement
241
+ case _:
242
+ pass
243
+ return aliases
244
+
245
+
246
+ def _loader_import_precedes(value: ast.expr, assignment: ast.stmt, context: PythonFileContext) -> bool:
247
+ root = value
248
+ while isinstance(root, ast.Attribute):
249
+ root = root.value
250
+ if not isinstance(root, ast.Name) or context.tree is None:
251
+ return False
252
+ if root.id == "__import__" and context.module_imports.builtin_is_unshadowed(root.id):
253
+ return True
254
+ return any(
255
+ (statement.lineno, statement.col_offset) < (assignment.lineno, assignment.col_offset)
256
+ and any((alias.asname or alias.name.split(".")[0]) == root.id for alias in statement.names)
257
+ for statement in context.tree.body
258
+ if isinstance(statement, (ast.Import, ast.ImportFrom))
259
+ )
260
+
261
+
262
+ def _binding_count(name: str, context: PythonFileContext) -> int:
263
+ return (
264
+ sum(
265
+ _binds_name(node, name) and _is_module_binding(node, context)
266
+ for node in context.nodes(
267
+ ast.Name,
268
+ ast.alias,
269
+ ast.FunctionDef,
270
+ ast.AsyncFunctionDef,
271
+ ast.ClassDef,
272
+ ast.ExceptHandler,
273
+ ast.MatchAs,
274
+ ast.MatchStar,
275
+ ast.MatchMapping,
276
+ )
277
+ )
278
+ + sum(name in node.names for node in context.nodes(ast.Global))
279
+ + sum(node.target.id == name for node in context.nodes(ast.NamedExpr))
280
+ )
281
+
282
+
283
+ def _binds_name(node: ast.AST, name: str) -> bool:
284
+ match node:
285
+ case (
286
+ ast.Name(id=bound, ctx=(ast.Store() | ast.Del()))
287
+ | ast.FunctionDef(name=bound)
288
+ | ast.AsyncFunctionDef(name=bound)
289
+ | ast.ClassDef(name=bound)
290
+ | ast.ExceptHandler(name=bound)
291
+ | ast.MatchAs(name=bound)
292
+ | ast.MatchStar(name=bound)
293
+ | ast.MatchMapping(rest=bound)
294
+ ):
295
+ return bound == name
296
+ case ast.alias(name=imported, asname=alias):
297
+ return (alias or imported.partition(".")[0]) == name
298
+ case _:
299
+ return False
300
+
301
+
302
+ def _is_module_binding(node: ast.AST, context: PythonFileContext) -> bool:
303
+ while node in context.parents:
304
+ node = context.parents[node]
305
+ if isinstance(node, _LEXICAL_SCOPES):
306
+ return isinstance(node, ast.Module)
307
+ return False