sarj-python-lint 1.108.1__tar.gz → 1.108.51__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 (319) hide show
  1. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/PKG-INFO +2 -2
  2. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/pyproject.toml +2 -2
  3. sarj_python_lint-1.108.51/src/sarj_python_lint/rules/_loguru.py +17 -0
  4. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_resource_provenance.py +9 -1
  5. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_async_function_cache.py +6 -1
  6. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_loguru_exc_info.py +33 -14
  7. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_loguru_printf_format.py +2 -9
  8. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_secret_in_log.py +90 -16
  9. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +63 -5
  10. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +2 -0
  11. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/.gitignore +0 -0
  12. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/LICENSE +0 -0
  13. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/README.md +0 -0
  14. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/__init__.py +0 -0
  15. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/__main__.py +0 -0
  16. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_analysis_session.py +0 -0
  17. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix.py +0 -0
  18. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  19. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  20. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_comments.py +0 -0
  21. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  22. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  23. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_docs.py +0 -0
  24. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_enums.py +0 -0
  25. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  26. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_inert.py +0 -0
  27. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  28. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  29. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_security.py +0 -0
  30. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  31. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_autofix_tests.py +0 -0
  32. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_file_context.py +0 -0
  33. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_filesystem.py +0 -0
  34. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_fixes.py +0 -0
  35. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_parallel.py +0 -0
  36. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_prepared_sources.py +0 -0
  37. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_python_target.py +0 -0
  38. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  39. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_rule_descriptors.py +0 -0
  40. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_secret_names.py +0 -0
  41. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_source.py +0 -0
  42. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/_version.py +0 -0
  43. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/application_logging.py +0 -0
  44. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/autofix.py +0 -0
  45. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/benchmark.py +0 -0
  46. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/cast_access.py +0 -0
  47. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/interpreter_argv.py +0 -0
  48. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/json_boundary.py +0 -0
  49. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/py.typed +0 -0
  50. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/ratchet.py +0 -0
  51. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rule_base.py +0 -0
  52. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rule_metadata.py +0 -0
  53. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/__init__.py +0 -0
  54. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  55. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  56. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  57. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  58. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  59. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  60. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  61. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  62. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  63. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_comments.py +0 -0
  64. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  65. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  66. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  67. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  68. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  69. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  70. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_exports.py +0 -0
  71. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  72. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_first_party.py +0 -0
  73. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  74. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_imports.py +0 -0
  75. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  76. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  77. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  78. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_local_source.py +0 -0
  79. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_logging.py +0 -0
  80. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  81. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  82. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  83. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  84. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  85. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  86. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  87. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_paths.py +0 -0
  88. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_postgres.py +0 -0
  89. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_postgres_ctas.py +0 -0
  90. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_project_index.py +0 -0
  91. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  92. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  93. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_pytest.py +0 -0
  94. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_registry.py +0 -0
  95. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  96. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  97. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  98. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_runtime_names.py +0 -0
  99. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
  100. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  101. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  102. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_sql.py +0 -0
  103. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  104. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  105. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  106. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  107. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  108. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  109. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  110. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  111. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  112. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  113. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  114. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  115. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  116. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  117. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  118. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  119. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  120. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  121. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  122. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  123. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  124. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  125. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  126. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  127. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  128. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  129. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  130. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  131. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  132. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  133. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  134. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  135. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  136. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  137. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  138. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  139. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  140. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  141. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
  142. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  143. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  144. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  145. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  146. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  147. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  148. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  149. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  150. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  151. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  152. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  153. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  154. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  155. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  156. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  157. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  158. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  159. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  160. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  161. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  162. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_dynamic_builtins.py +0 -0
  163. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  164. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  165. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  166. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  167. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  168. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  169. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  170. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  171. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  172. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
  173. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  174. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  175. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  176. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  177. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  178. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  179. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  180. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  181. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  182. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  183. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  184. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  185. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
  186. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  187. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  188. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  189. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  190. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  191. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  192. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  193. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  194. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  195. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  196. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
  197. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  198. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  199. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  200. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  201. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  202. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  203. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  204. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  205. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  206. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  207. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  208. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_shared_mutable_default.py +0 -0
  209. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  210. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  211. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  212. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_sys_path_mutation.py +0 -0
  213. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  214. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
  215. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  216. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  217. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  218. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  219. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  220. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  221. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  222. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  223. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  224. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  225. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  226. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  227. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  228. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  229. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  230. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  231. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  232. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  233. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  234. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  235. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  236. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  237. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  238. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  239. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  240. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  241. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  242. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  243. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  244. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  245. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  246. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  247. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  248. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  249. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  250. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  251. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  252. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  253. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  254. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  255. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  256. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  257. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  258. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  259. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  260. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
  261. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  262. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  263. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  264. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  265. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  266. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  267. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  268. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  269. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  270. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  271. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  272. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  273. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  274. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  275. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  276. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  277. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  278. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  279. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  280. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  281. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  282. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  283. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  284. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  285. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  286. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  287. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  288. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
  289. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  290. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  291. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  292. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  293. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  294. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  295. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  296. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  297. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  298. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  299. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  300. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  301. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  302. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  303. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
  304. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  305. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/stepdown.py +0 -0
  306. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  307. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  308. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  309. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  310. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  311. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  312. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  313. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  314. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  315. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  316. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  317. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  318. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  319. {sarj_python_lint-1.108.1 → sarj_python_lint-1.108.51}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.108.1
3
+ Version: 1.108.51
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/
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
19
19
  Requires-Python: >=3.14
20
20
  Requires-Dist: packaging==26.3
21
21
  Requires-Dist: ruff>=0.17.2
22
- Requires-Dist: sarj-rule-contracts==0.6.1
22
+ Requires-Dist: sarj-rule-contracts==0.6.2
23
23
  Requires-Dist: sqlglot==30.24.0
24
24
  Requires-Dist: typer==0.27.3
25
25
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.108.1"
3
+ version = "1.108.51"
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" }]
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Programming Language :: Python :: 3.14",
16
16
  "Topic :: Software Development :: Quality Assurance",
17
17
  ]
18
- dependencies = ["sarj-rule-contracts==0.6.1", "packaging==26.3", "ruff>=0.17.2", "sqlglot==30.24.0", "typer==0.27.3"]
18
+ dependencies = ["sarj-rule-contracts==0.6.2", "packaging==26.3", "ruff>=0.17.2", "sqlglot==30.24.0", "typer==0.27.3"]
19
19
 
20
20
  [project.scripts]
21
21
  sarj-python-lint = "sarj_python_lint.__main__:main"
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from typing import TYPE_CHECKING
5
+
6
+
7
+ if TYPE_CHECKING:
8
+ from sarj_python_lint._file_context import PythonFileContext
9
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
10
+
11
+
12
+ def logger_imports_untouched(context: PythonFileContext, provenance: ResourceProvenance) -> bool:
13
+ for node in context.nodes(ast.Name, ast.Attribute):
14
+ qualified = context.imports.resolved_qualified_name(node)
15
+ if qualified in {"loguru", "loguru.logger"} and not provenance.imported(node, qualified):
16
+ return False
17
+ return True
@@ -26,6 +26,7 @@ _RESOURCE_OWNERS = MappingProxyType(
26
26
  "httpx": frozenset({"Client", "AsyncClient"}),
27
27
  "asyncio": frozenset({"sleep", "CancelledError", "exceptions.CancelledError"}),
28
28
  "contextlib": frozenset({"ExitStack", "AsyncExitStack", "_BaseExitStack"}),
29
+ "functools": frozenset({"cache", "lru_cache", "cached_property"}),
29
30
  "subprocess": frozenset({"Popen", "TimeoutExpired"}),
30
31
  "re": frozenset({"compile", "Match", "Pattern"}),
31
32
  "operator": frozenset({"setitem", "delitem", "iadd", "iconcat", "imul", "iand", "isub", "ixor", "ior"}),
@@ -309,7 +310,14 @@ class ResourceProvenance:
309
310
  isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
310
311
  and node in parent.decorator_list
311
312
  and self.context.imports.resolved_qualified_name(node)
312
- in {"typing.final", "typing_extensions.final", "dataclasses.dataclass"}
313
+ in {
314
+ "typing.final",
315
+ "typing_extensions.final",
316
+ "dataclasses.dataclass",
317
+ "functools.cache",
318
+ "functools.lru_cache",
319
+ "functools.cached_property",
320
+ }
313
321
  )
314
322
  )
315
323
 
@@ -50,7 +50,8 @@ class NoAsyncFunctionCache(Rule):
50
50
  "Async generators, transformed functions, ambiguous or rebound imports, and generated files are excluded.",
51
51
  "Wildcard imports and names rebound by relative imports or imports outside the module body are excluded conservatively.",
52
52
  "lru_cache with disabled, dynamic, or unpacked maxsize arguments is excluded; an omitted size or literal positive integer or None is required.",
53
- "Indirect wrapping calls and runtime monkeypatches are not resolved. Test code is checked because coroutine reuse fails there too.",
53
+ "Visible mutations, deletion, or escape of functools providers invalidate their native identity conservatively.",
54
+ "Indirect wrapping calls and opaque runtime or interprocedural mutations are not resolved. Test code is checked because coroutine reuse fails there too.",
54
55
  "No automatic rewrite is offered because cache lifetime, cancellation, and concurrency require an explicit design decision.",
55
56
  ),
56
57
  examples=(
@@ -99,6 +100,10 @@ class NoAsyncFunctionCache(Rule):
99
100
  or is_suppressed(context.source_lines, decorator.lineno, self.code)
100
101
  ):
101
102
  continue
103
+ target = decorator.func if isinstance(decorator, ast.Call) else decorator
104
+ qualified = context.imports.resolved_qualified_name(target)
105
+ if qualified is None or not context.static_resource_provenance.imported(target, qualified):
106
+ continue
102
107
  diagnostics.append(
103
108
  Diagnostic(
104
109
  path=context.path,
@@ -18,10 +18,12 @@ from sarj_python_lint.rule_base import (
18
18
  is_suppressed,
19
19
  )
20
20
  from sarj_python_lint.rules._imports import ambiguous_import_names as _ambiguous_import_names
21
+ from sarj_python_lint.rules._loguru import logger_imports_untouched
21
22
 
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from sarj_python_lint._file_context import PythonFileContext
26
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
25
27
 
26
28
 
27
29
  _METHODS = frozenset({"trace", "debug", "info", "success", "warning", "error", "critical", "log"})
@@ -46,8 +48,8 @@ class NoLoguruExcInfo(Rule):
46
48
  "Safe fixes handle only a direct proven Loguru logger call with literal arguments and keywords, replacing unused exc_info=True with opt(exception=True). Existing bind/opt chains, dynamic argument evaluation and commented calls remain review-only.",
47
49
  "Only unambiguous Loguru imports and immediate bind/opt chains with literal exc_info=True are checked.",
48
50
  "A literal message is required; templates using exc_info or the record, explicit exception capture, and exception() are excluded.",
49
- "Unknown receivers, stored logger aliases, dynamic exc_info values, ambiguous imports, and generated files are excluded.",
50
- "Runtime monkeypatches of Loguru objects or methods are not resolved.",
51
+ "Unknown receivers, stored logger aliases, dynamic exc_info values, ambiguous or escaped imports, and generated files are excluded.",
52
+ "Visible namespace or logger mutations invalidate import proof. Interprocedural effects and runtime monkeypatches are not resolved.",
51
53
  "An intentionally stored field can use a diagnostic-specific suppression. More complex logging context remains review-only.",
52
54
  ),
53
55
  examples=(
@@ -121,18 +123,22 @@ class NoLoguruExcInfo(Rule):
121
123
  shadowed = _ambiguous_import_names(context)
122
124
  if "*" in shadowed:
123
125
  return []
126
+ provenance: ResourceProvenance | None = None
124
127
  diagnostics: list[Diagnostic] = []
125
128
  for call in context.nodes(ast.Call):
126
129
  if not isinstance(call.func, ast.Attribute) or call.func.attr not in _METHODS:
127
130
  continue
128
- keyword = next((item for item in call.keywords if item.arg == "exc_info"), None)
129
- if keyword is None or not isinstance(keyword.value, ast.Constant) or keyword.value.value is not True:
131
+ keyword = _unused_exc_info_keyword(call)
132
+ if keyword is None or is_suppressed(context.source_lines, keyword.lineno, self.code):
130
133
  continue
131
- if (
132
- not _is_unconfigured_loguru(call.func.value, context, shadowed)
133
- or not _has_unused_exc_info(call)
134
- or is_suppressed(context.source_lines, keyword.lineno, self.code)
135
- ):
134
+ receiver = _unconfigured_loguru_root(call.func.value, context, shadowed)
135
+ if receiver is None:
136
+ continue
137
+ if provenance is None:
138
+ provenance = context.static_resource_provenance
139
+ if not logger_imports_untouched(context, provenance):
140
+ return []
141
+ if not provenance.imported(receiver, "loguru.logger"):
136
142
  continue
137
143
  diagnostics.append(
138
144
  Diagnostic(
@@ -147,12 +153,23 @@ class NoLoguruExcInfo(Rule):
147
153
  return diagnostics
148
154
 
149
155
 
150
- def _is_unconfigured_loguru(receiver: ast.expr, context: PythonFileContext, shadowed: frozenset[str]) -> bool:
156
+ def _unused_exc_info_keyword(call: ast.Call) -> ast.keyword | None:
157
+ keyword = next((item for item in call.keywords if item.arg == "exc_info"), None)
158
+ if keyword is None or not isinstance(keyword.value, ast.Constant) or keyword.value.value is not True:
159
+ return None
160
+ if not _has_unused_exc_info(call):
161
+ return None
162
+ return keyword
163
+
164
+
165
+ def _unconfigured_loguru_root(
166
+ receiver: ast.expr, context: PythonFileContext, shadowed: frozenset[str]
167
+ ) -> ast.expr | None:
151
168
  opted = False
152
169
  while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
153
170
  method = receiver.func.attr
154
171
  if method not in {"bind", "opt"}:
155
- return False
172
+ return None
156
173
  if method == "opt" and not opted:
157
174
  opted = True
158
175
  if any(
@@ -163,14 +180,16 @@ def _is_unconfigured_loguru(receiver: ast.expr, context: PythonFileContext, shad
163
180
  )
164
181
  for keyword in receiver.keywords
165
182
  ):
166
- return False
183
+ return None
167
184
  receiver = receiver.func.value
168
185
  root = receiver
169
186
  while isinstance(root, ast.Attribute):
170
187
  root = root.value
171
- return not (isinstance(root, ast.Name) and root.id in shadowed) and context.imports.resolves(
188
+ if (isinstance(root, ast.Name) and root.id in shadowed) or not context.imports.resolves(
172
189
  receiver, sources=_LOGURU, symbol="logger"
173
- )
190
+ ):
191
+ return None
192
+ return receiver
174
193
 
175
194
 
176
195
  def _has_unused_exc_info(call: ast.Call) -> bool:
@@ -18,6 +18,7 @@ from sarj_python_lint.rule_base import (
18
18
  Severity,
19
19
  is_suppressed,
20
20
  )
21
+ from sarj_python_lint.rules._loguru import logger_imports_untouched
21
22
 
22
23
 
23
24
  if TYPE_CHECKING:
@@ -121,7 +122,7 @@ class NoLoguruPrintfFormat(Rule):
121
122
  continue
122
123
  if provenance is None:
123
124
  provenance = context.static_resource_provenance
124
- if not _logger_imports_untouched(context, provenance):
125
+ if not logger_imports_untouched(context, provenance):
125
126
  return []
126
127
  if not provenance.imported(receiver, "loguru.logger") or is_suppressed(
127
128
  context.source_lines, call.lineno, self.code
@@ -140,14 +141,6 @@ class NoLoguruPrintfFormat(Rule):
140
141
  return diagnostics
141
142
 
142
143
 
143
- def _logger_imports_untouched(context: PythonFileContext, provenance: ResourceProvenance) -> bool:
144
- for node in context.nodes(ast.Name, ast.Attribute):
145
- qualified = context.imports.resolved_qualified_name(node)
146
- if qualified in {"loguru", "loguru.logger"} and not provenance.imported(node, qualified):
147
- return False
148
- return True
149
-
150
-
151
144
  def _logger_root(receiver: ast.expr) -> ast.expr | None:
152
145
  while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
153
146
  if (
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
+ from dataclasses import dataclass
4
5
  from pathlib import PurePosixPath
5
6
  from typing import TYPE_CHECKING, ClassVar, override
6
7
 
@@ -15,6 +16,7 @@ from sarj_python_lint.rule_base import (
15
16
  RuleExample,
16
17
  )
17
18
  from sarj_python_lint.rules._logging import LOG_METHODS, is_logger_expr
19
+ from sarj_python_lint.rules._loguru import logger_imports_untouched
18
20
 
19
21
 
20
22
  if TYPE_CHECKING:
@@ -22,6 +24,7 @@ if TYPE_CHECKING:
22
24
  from pathlib import Path
23
25
 
24
26
  from sarj_python_lint._file_context import PythonFileContext
27
+ from sarj_python_lint.rules._resource_provenance import ResourceProvenance
25
28
 
26
29
 
27
30
  # A whole redaction token (`token_prefix`, `password_hash`, `secret_masked`,
@@ -30,6 +33,16 @@ if TYPE_CHECKING:
30
33
  _REDACTION_TOKENS = frozenset(
31
34
  {"prefix", "suffix", "redact", "redacted", "mask", "masked", "hash", "hint", "len", "length", "tag"}
32
35
  )
36
+ _LOGURU_OPT_PARAMETERS = frozenset({"exception", "record", "lazy", "colors", "raw", "capture", "depth", "ansi"})
37
+ _LOGURU_LOG_METHODS = frozenset(
38
+ {"trace", "debug", "info", "success", "warning", "error", "critical", "exception", "log"}
39
+ )
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class _OptBindingCandidate:
44
+ root: ast.expr
45
+ values: tuple[ast.expr, ...]
33
46
 
34
47
 
35
48
  def _is_raw_secret_reference(value: ast.expr) -> bool:
@@ -89,6 +102,9 @@ class NoSecretInLog(Rule):
89
102
  category=RuleCategory.SECURITY,
90
103
  limitations=(
91
104
  "Detection covers direct secret-named message and argument values, terminal attributes, constant secret-key subscripts, identity slices, f-string and literal format arguments, string concatenation, nested literal containers, literal keyword unpacking, and immediate structured-logger bindings.",
105
+ "Immediate opt calls on a proven untouched native Loguru logger expose only the first bind's values. Positional or expanded chain arguments, unsupported opt keyword names, custom wrappers, stored aliases, and visibly mutated or escaped imports are excluded from this added coverage.",
106
+ "This added coverage requires a native Loguru logging method and a positional message, with no terminal keyword or expanded arguments. Terminal fields are excluded even with capture=False, rather than evaluating option state or merging fields that may override bindings.",
107
+ "Earlier inner bindings are not merged: a safe outer binding hides earlier bindings, including nonconflicting fields. Opaque runtime effects remain unresolved.",
92
108
  "Logger recognition and secret classification are lexical; general aliases, arbitrary calls, dynamic containers, and interprocedural dataflow are not inspected.",
93
109
  ),
94
110
  examples=(
@@ -125,28 +141,24 @@ class NoSecretInLog(Rule):
125
141
  tree = context.tree
126
142
  if tree is None:
127
143
  return []
144
+ provenance: ResourceProvenance | None = None
145
+ untouched = False
128
146
  diags: list[Diagnostic] = []
129
147
  for node in context.nodes(ast.Call):
130
148
  function = node.func
131
149
  if not isinstance(function, ast.Attribute) or not _is_logging_call(node):
132
150
  continue
133
- message_index = 1 if function.attr == "log" else 0
134
- for arg in node.args[message_index:]:
135
- diags.extend(
136
- Diagnostic(
137
- path=path,
138
- line=unsafe.lineno,
139
- col=unsafe.col_offset + 1,
140
- code=self.code,
141
- message="Credential-like reference is interpolated into a log message and may expose sensitive data — omit it.",
142
- )
143
- for unsafe in _unsafe_message_values(arg)
144
- )
151
+ _collect_message_secrets(path, node, self.code, diags)
145
152
  _collect_keyword_secrets(path, node, self.code, diags)
146
- diags.extend(
147
- _diagnostic(path, value, self.code, "structured logger binding")
148
- for value in _bound_logger_values(function.value)
149
- )
153
+ bound_values = _bound_logger_values(function.value)
154
+ candidate = _opt_binding_candidate(node)
155
+ if candidate is not None:
156
+ if provenance is None:
157
+ provenance = context.static_resource_provenance
158
+ untouched = logger_imports_untouched(context, provenance)
159
+ if untouched and provenance.imported(candidate.root, "loguru.logger"):
160
+ bound_values = candidate.values
161
+ diags.extend(_diagnostic(path, value, self.code, "structured logger binding") for value in bound_values)
150
162
  return diags
151
163
 
152
164
 
@@ -158,6 +170,50 @@ def _bound_logger_values(receiver: ast.expr) -> tuple[ast.expr, ...]:
158
170
  return tuple(secret for keyword in receiver.keywords for secret in _unsafe_message_values(keyword.value))
159
171
 
160
172
 
173
+ def _opt_binding_candidate(call: ast.Call) -> _OptBindingCandidate | None:
174
+ receiver = _loguru_terminal_receiver(call)
175
+ if receiver is None:
176
+ return None
177
+ binding = receiver
178
+ if not isinstance(binding, ast.Call) or not isinstance(binding.func, ast.Attribute) or binding.func.attr != "opt":
179
+ return None
180
+ while isinstance(binding, ast.Call) and isinstance(binding.func, ast.Attribute) and binding.func.attr == "opt":
181
+ binding = binding.func.value
182
+ if not isinstance(binding, ast.Call) or not isinstance(binding.func, ast.Attribute) or binding.func.attr != "bind":
183
+ return None
184
+ values = _bound_logger_values(binding)
185
+ if not values:
186
+ return None
187
+ root = _loguru_chain_root(receiver)
188
+ if root is None:
189
+ return None
190
+ return _OptBindingCandidate(root=root, values=values)
191
+
192
+
193
+ def _loguru_terminal_receiver(call: ast.Call) -> ast.expr | None:
194
+ function = call.func
195
+ if (
196
+ not isinstance(function, ast.Attribute)
197
+ or function.attr not in _LOGURU_LOG_METHODS
198
+ or len(call.args) <= int(function.attr == "log")
199
+ or call.keywords
200
+ or any(isinstance(argument, ast.Starred) for argument in call.args)
201
+ ):
202
+ return None
203
+ return function.value
204
+
205
+
206
+ def _loguru_chain_root(receiver: ast.expr) -> ast.expr | None:
207
+ root = receiver
208
+ while isinstance(root, ast.Call) and isinstance(root.func, ast.Attribute):
209
+ if root.func.attr not in {"bind", "opt"} or root.args or any(keyword.arg is None for keyword in root.keywords):
210
+ return None
211
+ if root.func.attr == "opt" and any(keyword.arg not in _LOGURU_OPT_PARAMETERS for keyword in root.keywords):
212
+ return None
213
+ root = root.func.value
214
+ return root
215
+
216
+
161
217
  def _diagnostic(path: Path, value: ast.expr, code: str, context: str) -> Diagnostic:
162
218
  return Diagnostic(
163
219
  path=path,
@@ -183,6 +239,24 @@ def _unsafe_formatted_values(parts: list[ast.expr]) -> tuple[ast.expr, ...]:
183
239
  )
184
240
 
185
241
 
242
+ def _collect_message_secrets(path: Path, node: ast.Call, code: str, diags: list[Diagnostic]) -> None:
243
+ function = node.func
244
+ if not isinstance(function, ast.Attribute):
245
+ return
246
+ message_index = 1 if function.attr == "log" else 0
247
+ for arg in node.args[message_index:]:
248
+ diags.extend(
249
+ Diagnostic(
250
+ path=path,
251
+ line=unsafe.lineno,
252
+ col=unsafe.col_offset + 1,
253
+ code=code,
254
+ message="Credential-like reference is interpolated into a log message and may expose sensitive data — omit it.",
255
+ )
256
+ for unsafe in _unsafe_message_values(arg)
257
+ )
258
+
259
+
186
260
  def _collect_keyword_secrets(path: Path, node: ast.Call, code: str, diags: list[Diagnostic]) -> None:
187
261
  for kw in node.keywords:
188
262
  if kw.arg is None:
@@ -22,6 +22,7 @@ from sarj_python_lint.rule_base import (
22
22
  )
23
23
  from sarj_python_lint.rules._annotation_semantics import bit_or_members as _flatten_union
24
24
  from sarj_python_lint.rules._ast_index import nodes, parent_map, walk as walk_ast
25
+ from sarj_python_lint.rules._imports import ABC_SOURCES
25
26
  from sarj_python_lint.rules._inherited_contract import inherited_contract
26
27
  from sarj_python_lint.rules._nominal_project import NominalSource
27
28
 
@@ -160,9 +161,48 @@ class PreferNominalIdTypes(Rule):
160
161
  "Non-ID role discovery uses exact snake-case names and proven matching carriers; it does not infer brands from names alone. Dependency discovery respects declared workspace members and exclusions, and excludes optional and transitive dependencies.",
161
162
  "Rebound aliases, generic aliases, carrier names shadowed by type parameters, and mutated or escaped canonical provider namespaces are skipped conservatively across the file.",
162
163
  "Generated code, migrations, external adapters, operational context, raw schemas, SQLAlchemy Mapped fields, ambiguous imports, and unlike carrier shapes are excluded.",
163
- "Classes with explicit bases, including object, are excluded without requiring an override decorator. Local base declarations do not prove runtime member ownership remains closed; class construction, native hooks and escaped handles can change inheritance. Independent module functions and plain classes remain eligible.",
164
+ "Canonical object, ABC, Protocol and Generic bases do not hide fields or methods declared by the class. Mutated, escaped or rebound providers remain unknown. Other explicit bases stay excluded without requiring an override decorator; their inherited API may prescribe raw carriers. Local custom bases do not prove runtime member ownership remains closed.",
164
165
  ),
165
166
  examples=(
167
+ RuleExample(
168
+ example_id="owned-abstract-contract-id-roles",
169
+ scenario="owned-contract",
170
+ title="Abstract contracts declare their own nominal ID boundary",
171
+ outcome=ExampleOutcome.MATCH,
172
+ files=(
173
+ ExampleFile.python(
174
+ "app/contracts.py",
175
+ "from abc import ABC, abstractmethod\n"
176
+ "class FilePort(ABC):\n"
177
+ " @abstractmethod\n"
178
+ " def move(self, file_id: str, folder_id: str) -> None: ...\n",
179
+ ),
180
+ ),
181
+ focus_path=PurePosixPath("app/contracts.py"),
182
+ expected_count=1,
183
+ public=True,
184
+ ),
185
+ RuleExample(
186
+ example_id="owned-abstract-contract-nominal-ids",
187
+ scenario="owned-contract",
188
+ title="Abstract contracts use distinct domain identifiers",
189
+ outcome=ExampleOutcome.NO_MATCH,
190
+ files=(
191
+ ExampleFile.python(
192
+ "app/contracts.py",
193
+ "from abc import ABC, abstractmethod\n"
194
+ "from typing import NewType\n"
195
+ "FileId = NewType('FileId', str)\n"
196
+ "FolderId = NewType('FolderId', str)\n"
197
+ "class FilePort(ABC):\n"
198
+ " @abstractmethod\n"
199
+ " def move(self, file_id: FileId, folder_id: FolderId) -> None: ...\n",
200
+ ),
201
+ ),
202
+ focus_path=PurePosixPath("app/contracts.py"),
203
+ expected_count=0,
204
+ public=True,
205
+ ),
166
206
  RuleExample(
167
207
  example_id="swappable-primitive-id-roles",
168
208
  title="Two domain ID roles share the same primitive carrier",
@@ -289,7 +329,7 @@ class PreferNominalIdTypes(Rule):
289
329
 
290
330
  imports = context.imports
291
331
  original_facts = _type_facts(tree, imports, node_index=context.node_index)
292
- boundaries = _boundary_nodes(tree, imports)
332
+ boundaries = _boundary_nodes(tree, imports, original_facts)
293
333
  facts = (
294
334
  self._expanded_facts(context, tree, imports)
295
335
  if any(_can_expand(node, imports, original_facts) for node in boundaries)
@@ -388,6 +428,7 @@ def _is_excluded_path(path: Path) -> bool:
388
428
  def _boundary_nodes(
389
429
  tree: ast.Module,
390
430
  imports: ImportIndex,
431
+ facts: _TypeFacts,
391
432
  ) -> list[ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef]:
392
433
  result: list[ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef] = []
393
434
  for statement in tree.body:
@@ -397,7 +438,7 @@ def _boundary_nodes(
397
438
  continue
398
439
  if not isinstance(statement, ast.ClassDef):
399
440
  continue
400
- if inherited_contract(statement):
441
+ if inherited_contract(statement) and not _has_only_marker_bases(statement, imports, facts.shadowed_names):
401
442
  continue
402
443
  if _name_parts(statement.name) & _OPERATIONAL_NAME_PARTS:
403
444
  continue
@@ -408,6 +449,23 @@ def _boundary_nodes(
408
449
  return result
409
450
 
410
451
 
452
+ def _has_only_marker_bases(node: ast.ClassDef, imports: ImportIndex, shadowed_names: frozenset[str]) -> bool:
453
+ for expression in node.bases:
454
+ base = expression.value if isinstance(expression, ast.Subscript) else expression
455
+ if _expression_is_shadowed(base, shadowed_names):
456
+ return False
457
+ if isinstance(base, ast.Name) and base.id == "object" and imports.builtin_is_unshadowed("object"):
458
+ continue
459
+ if imports.resolves(base, sources=_BUILTIN_SOURCES, symbol="object"):
460
+ continue
461
+ if imports.resolves(base, sources=ABC_SOURCES, symbol="ABC"):
462
+ continue
463
+ if imports.resolved_symbol(base, sources=_TYPING_SOURCES) in {"Generic", "Protocol"}:
464
+ continue
465
+ return False
466
+ return True
467
+
468
+
411
469
  def _is_operational_function(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
412
470
  return bool(_name_parts(node.name) & _OPERATIONAL_NAME_PARTS)
413
471
 
@@ -725,7 +783,7 @@ def _is_builtin_namespace_call(node: ast.Call, imports: ImportIndex) -> bool:
725
783
 
726
784
 
727
785
  def _escaped_provider_names(tree: ast.Module, imports: ImportIndex, *, node_index: NodeIndex | None) -> set[str]:
728
- providers = _TYPING_SOURCES | _BUILTIN_SOURCES | _UUID_SOURCES
786
+ providers = _TYPING_SOURCES | _BUILTIN_SOURCES | _UUID_SOURCES | ABC_SOURCES
729
787
  namespaces = {
730
788
  name: binding.module
731
789
  for name, binding in imports.bindings.items()
@@ -763,7 +821,7 @@ def _mutated_builtin_names(node: ast.expr, member: str | None, imports: ImportIn
763
821
  binding = imports.bindings.get(node.id)
764
822
  if binding is None or binding.module != "builtins" or binding.symbol is not None:
765
823
  return set()
766
- carriers = {"str", "int"} | _BUILTIN_COLLECTIONS
824
+ carriers = {"str", "int", "object"} | _BUILTIN_COLLECTIONS
767
825
  changed = carriers if member is None or member.startswith("__") else carriers & {member}
768
826
  if not changed:
769
827
  return set()
@@ -313,6 +313,8 @@ class _Analysis:
313
313
  state.constants[target.id] = value
314
314
 
315
315
  def executions(self, statement: ast.stmt, state: _State) -> None:
316
+ if not state.bindings:
317
+ return
316
318
  for node in _eager_nodes(statement):
317
319
  if (
318
320
  not isinstance(node, ast.Call)