sarj-python-lint 1.9.1__tar.gz → 1.10.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 (167) hide show
  1. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_database_programmability.py +2 -2
  5. sarj_python_lint-1.10.0/src/sarj_python_lint/rules/no_injected_module_loader.py +213 -0
  6. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/.gitignore +0 -0
  7. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/LICENSE +0 -0
  8. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/README.md +0 -0
  9. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/_version.py +0 -0
  18. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/benchmark.py +0 -0
  19. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/json_boundary.py +0 -0
  20. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/py.typed +0 -0
  21. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/ratchet.py +0 -0
  22. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rule_base.py +0 -0
  23. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  24. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  25. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  26. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  27. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  28. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  29. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  30. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  31. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  32. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  33. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  34. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  35. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  36. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  37. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  38. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  39. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  40. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  41. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  42. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  43. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  44. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  45. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  46. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  47. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  48. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  49. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  50. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  51. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
  52. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  53. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  54. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  55. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  56. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  57. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  58. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  59. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  60. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  61. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  62. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  63. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  64. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  65. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  66. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  67. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  68. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  69. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  70. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  71. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  72. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  73. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  74. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  75. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  76. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  77. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  78. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  79. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  80. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  81. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  82. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  83. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  84. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  85. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  86. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  87. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  88. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  89. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  90. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  91. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  92. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  93. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  94. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  95. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  96. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  97. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  98. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  99. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  100. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  101. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  102. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  103. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  104. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  105. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  106. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  107. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  108. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  109. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  110. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  111. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  112. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  113. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  114. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  115. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  116. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  117. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  118. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  119. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  120. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  121. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  122. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  123. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  124. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  125. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  126. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  127. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  128. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  129. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  130. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  131. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  132. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  133. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  134. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  135. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  136. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  137. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  138. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  139. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  140. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  141. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  142. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  143. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  144. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  145. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  146. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  147. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  148. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  149. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  150. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  151. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  152. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  153. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  154. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  155. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  156. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  157. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  158. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  159. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  160. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  161. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  162. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  163. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  164. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  165. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  166. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  167. {sarj_python_lint-1.9.1 → sarj_python_lint-1.10.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.9.1
3
+ Version: 1.10.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.9.1"
3
+ version = "1.10.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" }]
@@ -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,
@@ -38,7 +38,7 @@ class NoDatabaseProgrammability(Rule):
38
38
  id = "no-database-programmability"
39
39
  code = "SARJ470"
40
40
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
41
- default_level=Severity.WARNING,
41
+ default_level=Severity.ERROR,
42
42
  summary="Keep stored SQL functions and triggers out of embedded SQL.",
43
43
  rationale="SQL hidden in Python can bypass migration-only architecture rules. Procedural fault fixtures require extra database objects and cleanup when a declarative constraint suffices.",
44
44
  remediation="Use declarative constraints or explicit transactional application behavior, including in test fixtures. Use an exact SARJ470 suppression for an approved compatibility exception.",
@@ -91,7 +91,7 @@ class NoDatabaseProgrammability(Rule):
91
91
  line=node.lineno,
92
92
  col=node.col_offset + 1,
93
93
  code=self.code,
94
- severity=Severity.WARNING,
94
+ severity=Severity.ERROR,
95
95
  message="Stored SQL functions and triggers are prohibited by project architecture, including in test fixtures; use a declarative constraint or explicit transactional application code.",
96
96
  )
97
97
  )
@@ -0,0 +1,213 @@
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
24
+
25
+ from sarj_python_lint._file_context import PythonFileContext
26
+
27
+
28
+ _LOADER_TARGETS = frozenset({"importlib.import_module", "importlib.__import__", "builtins.__import__"})
29
+
30
+
31
+ @final
32
+ class NoInjectedModuleLoader(Rule):
33
+ id = "no-injected-module-loader"
34
+ code = "SARJ471"
35
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
36
+ default_level=Severity.WARNING,
37
+ summary="Keep Python module loaders out of application parameter defaults.",
38
+ rationale="Exposing Python's import machinery as an injectable dependency expands the callable contract and hides the concrete imported API behind a generic loader.",
39
+ 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.",
40
+ category=RuleCategory.ARCHITECTURE,
41
+ autofix=AutofixPolicy.NONE,
42
+ limitations=(
43
+ "Flags defaults resolving to importlib.import_module, importlib.__import__, or builtins.__import__, including stable module-level import and single-assignment aliases and unshadowed __import__.",
44
+ "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.",
45
+ "Conservatively skips shadowed or rebound names, wildcard-import files, and files that reassign loader attributes. No autofix: removing an argument changes the callable contract and moving imports can change initialization timing.",
46
+ ),
47
+ examples=(
48
+ RuleExample(
49
+ example_id="injected-loader",
50
+ title="Import machinery becomes a configurable parameter",
51
+ outcome=ExampleOutcome.MATCH,
52
+ files=(
53
+ ExampleFile.python(
54
+ "app/service.py",
55
+ "import importlib\ndef run(*, loader=importlib.import_module):\n return loader('plugin').create()\n",
56
+ ),
57
+ ),
58
+ focus_path=PurePosixPath("app/service.py"),
59
+ expected_count=1,
60
+ public=True,
61
+ ),
62
+ RuleExample(
63
+ example_id="lazy-import",
64
+ title="Implementation owns its lazy import",
65
+ outcome=ExampleOutcome.NO_MATCH,
66
+ files=(
67
+ ExampleFile.python(
68
+ "app/service.py", "def run():\n from plugin import create\n return create()\n"
69
+ ),
70
+ ),
71
+ focus_path=PurePosixPath("app/service.py"),
72
+ expected_count=0,
73
+ public=True,
74
+ ),
75
+ ),
76
+ )
77
+ description = documentation.summary
78
+
79
+ @override
80
+ def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
81
+ if (
82
+ context.tree is None
83
+ or context.generated
84
+ or is_test_path(context.path)
85
+ or is_test_support_path(context.path)
86
+ or _ambiguous_loader_bindings(context)
87
+ ):
88
+ return []
89
+ aliases = _stable_loader_aliases(context)
90
+ findings: list[Diagnostic] = []
91
+ for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda):
92
+ for argument, default in _defaults(function.args):
93
+ direct = _is_loader(default, context)
94
+ aliased = isinstance(default, ast.Name) and aliases.get(default.id, function.lineno) < function.lineno
95
+ if not (direct or aliased) or _shadowed(default, function, context):
96
+ continue
97
+ findings.append(
98
+ Diagnostic(
99
+ path=context.path,
100
+ line=argument.lineno,
101
+ col=argument.col_offset + 1,
102
+ code=self.code,
103
+ message=f"Parameter `{argument.arg}` exposes Python's module loader; import directly or pass a concrete domain dependency.",
104
+ severity=Severity.WARNING,
105
+ )
106
+ )
107
+ return sorted(findings, key=lambda finding: (finding.line, finding.col))
108
+
109
+
110
+ def _ambiguous_loader_bindings(context: PythonFileContext) -> bool:
111
+ if any(alias.name == "*" for node in context.nodes(ast.ImportFrom) for alias in node.names):
112
+ return True
113
+ return any(
114
+ isinstance(node.ctx, (ast.Store, ast.Del))
115
+ and context.module_imports.resolved_qualified_name(node) in _LOADER_TARGETS
116
+ for node in context.nodes(ast.Attribute)
117
+ )
118
+
119
+
120
+ def _defaults(arguments: ast.arguments) -> Iterator[tuple[ast.arg, ast.expr]]:
121
+ positional = (*arguments.posonlyargs, *arguments.args)
122
+ defaulted = positional[-len(arguments.defaults) :] if arguments.defaults else ()
123
+ yield from zip(defaulted, arguments.defaults, strict=True)
124
+ yield from (
125
+ (argument, default)
126
+ for argument, default in zip(arguments.kwonlyargs, arguments.kw_defaults, strict=True)
127
+ if default is not None
128
+ )
129
+
130
+
131
+ def _is_loader(default: ast.expr, context: PythonFileContext) -> bool:
132
+ if context.module_imports.resolved_qualified_name(default) in _LOADER_TARGETS:
133
+ return True
134
+ return (
135
+ isinstance(default, ast.Name)
136
+ and default.id == "__import__"
137
+ and context.module_imports.builtin_is_unshadowed("__import__")
138
+ )
139
+
140
+
141
+ def _shadowed(default: ast.expr, function: ast.AST, context: PythonFileContext) -> bool:
142
+ root = default
143
+ while isinstance(root, ast.Attribute):
144
+ root = root.value
145
+ if not isinstance(root, ast.Name):
146
+ return True
147
+ parent = context.parents.get(function)
148
+ while parent is not None:
149
+ if isinstance(parent, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
150
+ return True
151
+ if isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
152
+ if root.id in scope_bindings(parent):
153
+ return True
154
+ elif isinstance(parent, ast.ClassDef):
155
+ collector = LocalBindingCollector()
156
+ for statement in parent.body:
157
+ collector.visit(statement)
158
+ if root.id in collector.names:
159
+ return True
160
+ parent = context.parents.get(parent)
161
+ return False
162
+
163
+
164
+ def _stable_loader_aliases(context: PythonFileContext) -> dict[str, int]:
165
+ if context.tree is None:
166
+ return {}
167
+ aliases: dict[str, int] = {}
168
+ for statement in context.tree.body:
169
+ match statement:
170
+ case (
171
+ ast.Assign(targets=[ast.Name(id=name)], value=value)
172
+ | ast.AnnAssign(target=ast.Name(id=name), value=ast.expr() as value)
173
+ ):
174
+ if (
175
+ _is_loader(value, context)
176
+ and _loader_import_precedes(value, statement.lineno, context)
177
+ and _binding_count(name, context) == 1
178
+ ):
179
+ aliases[name] = statement.lineno
180
+ case _:
181
+ pass
182
+ return aliases
183
+
184
+
185
+ def _loader_import_precedes(value: ast.expr, line: int, context: PythonFileContext) -> bool:
186
+ root = value
187
+ while isinstance(root, ast.Attribute):
188
+ root = root.value
189
+ if not isinstance(root, ast.Name) or context.tree is None:
190
+ return False
191
+ if root.id == "__import__" and context.module_imports.builtin_is_unshadowed(root.id):
192
+ return True
193
+ return any(
194
+ statement.lineno < line
195
+ and any((alias.asname or alias.name.split(".")[0]) == root.id for alias in statement.names)
196
+ for statement in context.tree.body
197
+ if isinstance(statement, (ast.Import, ast.ImportFrom))
198
+ )
199
+
200
+
201
+ def _binding_count(name: str, context: PythonFileContext) -> int:
202
+ return (
203
+ sum(node.id == name and isinstance(node.ctx, (ast.Store, ast.Del)) for node in context.nodes(ast.Name))
204
+ + sum(node.arg == name for node in context.nodes(ast.arg))
205
+ + sum((node.asname or node.name.split(".")[0]) == name for node in context.nodes(ast.alias))
206
+ + sum(
207
+ node.name == name
208
+ for node in context.nodes(
209
+ ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.ExceptHandler, ast.MatchAs, ast.MatchStar
210
+ )
211
+ )
212
+ + sum(node.rest == name for node in context.nodes(ast.MatchMapping))
213
+ )