sarj-python-lint 1.85.0__tar.gz → 1.88.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 (302) hide show
  1. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/PKG-INFO +2 -2
  2. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/pyproject.toml +2 -2
  3. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/__main__.py +73 -16
  4. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_analysis_session.py +6 -2
  5. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_parallel.py +75 -14
  6. sarj_python_lint-1.88.0/src/sarj_python_lint/_prepared_sources.py +175 -0
  7. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_source.py +11 -3
  8. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/benchmark.py +15 -3
  9. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rule_base.py +6 -1
  10. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_local_source.py +41 -9
  11. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_project_index.py +115 -17
  12. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +2 -1
  13. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +3 -2
  14. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +2 -1
  15. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/.gitignore +0 -0
  16. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/LICENSE +0 -0
  17. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/README.md +0 -0
  18. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/__init__.py +0 -0
  19. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix.py +0 -0
  20. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
  21. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
  22. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
  23. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
  24. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
  25. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
  26. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
  27. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
  28. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
  29. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
  30. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
  31. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_security.py +0 -0
  32. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
  33. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
  34. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_file_context.py +0 -0
  35. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_filesystem.py +0 -0
  36. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_fixes.py +0 -0
  37. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_python_target.py +0 -0
  38. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  39. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_secret_names.py +0 -0
  40. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/_version.py +0 -0
  41. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/application_logging.py +0 -0
  42. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/autofix.py +0 -0
  43. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/cast_access.py +0 -0
  44. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  45. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/json_boundary.py +0 -0
  46. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/py.typed +0 -0
  47. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/ratchet.py +0 -0
  48. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  49. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  50. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  51. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  52. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  53. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  54. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
  55. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
  56. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  57. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
  58. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  59. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  60. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  61. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  62. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  63. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  64. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  65. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  66. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  67. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  68. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  69. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  70. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  71. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  72. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
  73. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  74. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  75. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  76. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  77. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
  78. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  79. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
  80. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
  81. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  82. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
  83. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  84. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  85. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  86. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  87. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  88. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  89. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  90. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  91. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
  92. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  93. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  94. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
  95. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
  96. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
  97. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  98. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
  99. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  100. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  101. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  102. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  103. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  104. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
  105. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  106. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  107. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
  108. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  109. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
  110. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
  111. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  112. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  113. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  114. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  115. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  116. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  117. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  118. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  119. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  120. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
  121. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  122. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  123. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  124. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  125. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  126. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  127. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  128. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
  129. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  130. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  131. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  132. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  133. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  134. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  135. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  136. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  137. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  138. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  139. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  140. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  141. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  142. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
  143. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  144. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  145. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  146. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
  147. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  148. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  149. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  150. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  151. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  152. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  153. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  154. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  155. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  156. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  157. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  158. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  159. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  160. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  161. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  162. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  163. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  164. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  165. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  166. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  167. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  168. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  169. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  170. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  171. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  172. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  173. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  174. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  175. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  176. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  177. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  178. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  179. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  180. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  181. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  182. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  183. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  184. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
  185. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
  186. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  187. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
  188. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  189. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  190. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  191. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  192. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  193. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  194. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  195. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  196. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  197. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  198. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  199. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  200. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  201. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  202. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  203. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  204. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  205. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  206. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  207. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  208. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  209. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  210. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  211. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  212. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  213. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  214. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
  215. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  216. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  217. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  218. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  219. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  220. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  221. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  222. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  223. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  224. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
  225. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  226. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  227. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  228. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  229. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  230. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  231. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  232. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  233. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  234. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  235. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  236. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  237. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  238. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  239. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
  240. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  241. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  242. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  243. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  244. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  245. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  246. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  247. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  248. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  249. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
  250. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  251. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  252. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  253. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  254. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  255. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
  256. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
  257. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  258. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  259. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  260. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  261. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  262. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  263. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  264. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  265. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  266. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  267. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  268. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  269. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  270. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  271. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  272. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  273. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  274. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  275. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  276. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  277. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  278. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  279. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  280. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  281. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  282. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  283. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  284. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  285. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  286. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  287. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  288. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  289. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  290. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  291. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
  292. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  293. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
  294. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  295. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  296. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  297. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  298. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  299. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  300. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  301. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  302. {sarj_python_lint-1.85.0 → sarj_python_lint-1.88.0}/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.85.0
3
+ Version: 1.88.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/
@@ -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.4.2
22
+ Requires-Dist: sarj-rule-contracts==0.5.0
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.85.0"
3
+ version = "1.88.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" }]
@@ -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.4.2", "packaging==26.3", "ruff>=0.17.2", "sqlglot==30.24.0", "typer==0.27.3"]
18
+ dependencies = ["sarj-rule-contracts==0.5.0", "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"
@@ -18,6 +18,7 @@ from sarj_python_lint import __version__
18
18
  from sarj_python_lint._analysis_session import AnalysisSession
19
19
  from sarj_python_lint._file_context import PythonFileContext
20
20
  from sarj_python_lint._filesystem import atomic_write_text
21
+ from sarj_python_lint._prepared_sources import PreparedSourceInputs
21
22
  from sarj_python_lint._source import read_python_source
22
23
  from sarj_python_lint.json_boundary import is_object_mapping, parse_json
23
24
  from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity, is_suppressed
@@ -30,6 +31,8 @@ from sarj_python_lint.suppression_policy import PROTECTED_SUPPRESSION_CODES
30
31
  if TYPE_CHECKING:
31
32
  from collections.abc import Sequence
32
33
 
34
+ from sarj_rule_contracts import SourceCatalog
35
+
33
36
 
34
37
  SKIP_DIR_NAMES = frozenset(
35
38
  {
@@ -101,7 +104,9 @@ def _canonical_rule_ids(rule_names: Sequence[str]) -> list[RuleId]:
101
104
  return list(dict.fromkeys(RuleId(name if name in REGISTRY else aliases.get(name, name)) for name in rule_names))
102
105
 
103
106
 
104
- def _check(rule_names: Sequence[str], paths: list[Path], *, jobs: int = 1) -> list[Diagnostic]:
107
+ def _check(
108
+ rule_names: Sequence[str], paths: list[Path], *, jobs: int = 1, sources: SourceCatalog | None = None
109
+ ) -> list[Diagnostic]:
105
110
  rule_ids = _canonical_rule_ids(rule_names)
106
111
  unknown = [rid for rid in rule_ids if rid not in REGISTRY]
107
112
  if unknown:
@@ -109,30 +114,81 @@ def _check(rule_names: Sequence[str], paths: list[Path], *, jobs: int = 1) -> li
109
114
  sys.stderr.write(f"available: {', '.join(sorted(REGISTRY))}\n")
110
115
  raise SystemExit(2)
111
116
  rules = [REGISTRY[rid]() for rid in rule_ids]
112
- session = AnalysisSession()
117
+ source_inputs = None if sources is None else PreparedSourceInputs(sources)
118
+ session = AnalysisSession() if source_inputs is None else AnalysisSession(source_inputs=source_inputs)
113
119
  for rule in rules:
114
120
  rule.prepare_session(session)
115
121
  clear_path_caches()
116
122
  expanded = expand_paths(paths)
117
- loaded = {path: read_python_source(path) for path in expanded}
118
- project_rules = [rule for rule in rules if isinstance(rule, ProjectRule)]
119
- if project_rules:
120
- indexes = ProjectIndexSet.build(expanded, loaded, facts=session.first_party)
121
- session.project = indexes
122
- for rule in project_rules:
123
- rule.prepare(indexes)
124
- if jobs > 1 and len(loaded) > 1:
125
- from sarj_python_lint._parallel import check_parallel # ruff: ignore[import-outside-top-level] -- serial API users do not need multiprocessing.
126
-
127
- parallel = check_parallel([type(rule) for rule in rules], loaded, session.project, jobs=jobs)
128
- if parallel is not None:
129
- return parallel
123
+ loaded = (
124
+ _load_python_sources(expanded, sources)
125
+ if source_inputs is None
126
+ else _load_python_sources(expanded, sources, source_inputs=source_inputs)
127
+ )
128
+ _prepare_project(rules, expanded, loaded, session, source_inputs=source_inputs)
129
+ parallel = _parallel_diagnostics(rules, loaded, session, jobs=jobs, source_inputs=source_inputs)
130
+ if parallel is not None:
131
+ return parallel
130
132
  diags: list[Diagnostic] = []
131
133
  for p, source in loaded.items():
132
134
  diags.extend(check_source(rules, p, source, session=session))
133
135
  return diags
134
136
 
135
137
 
138
+ def _load_python_sources(
139
+ paths: Sequence[Path], sources: SourceCatalog | None, *, source_inputs: PreparedSourceInputs | None = None
140
+ ) -> dict[Path, str]:
141
+ if sources is None:
142
+ return {path: read_python_source(path) for path in paths}
143
+ loaded = {path: read_python_source(path, sources=sources) for path in paths}
144
+ if source_inputs is not None:
145
+ for path in loaded:
146
+ snapshot = sources.get(path)
147
+ if snapshot is not None:
148
+ source_inputs.remember_selected(path, snapshot)
149
+ return loaded
150
+
151
+
152
+ def _prepare_project(
153
+ rules: list[Rule],
154
+ expanded: list[Path],
155
+ loaded: dict[Path, str],
156
+ session: AnalysisSession,
157
+ *,
158
+ source_inputs: PreparedSourceInputs | None,
159
+ ) -> None:
160
+ project_rules = [rule for rule in rules if isinstance(rule, ProjectRule)]
161
+ if not project_rules:
162
+ return
163
+ indexes = (
164
+ ProjectIndexSet.build(expanded, loaded, facts=session.first_party)
165
+ if source_inputs is None
166
+ else ProjectIndexSet.build(expanded, loaded, facts=session.first_party, source_inputs=source_inputs)
167
+ )
168
+ session.project = indexes
169
+ for rule in project_rules:
170
+ rule.prepare(indexes)
171
+
172
+
173
+ def _parallel_diagnostics(
174
+ rules: list[Rule],
175
+ loaded: dict[Path, str],
176
+ session: AnalysisSession,
177
+ *,
178
+ jobs: int,
179
+ source_inputs: PreparedSourceInputs | None,
180
+ ) -> list[Diagnostic] | None:
181
+ if jobs <= 1 or len(loaded) <= 1:
182
+ return None
183
+ from sarj_python_lint._parallel import check_parallel # ruff: ignore[import-outside-top-level] -- serial API users do not need multiprocessing.
184
+
185
+ if source_inputs is None:
186
+ return check_parallel([type(rule) for rule in rules], loaded, session.project, jobs=jobs)
187
+ return check_parallel(
188
+ [type(rule) for rule in rules], loaded, session.project, jobs=jobs, source_inputs=source_inputs
189
+ )
190
+
191
+
136
192
  def check_source(
137
193
  rules: list[Rule], p: Path, source: str, *, session: AnalysisSession | None = None
138
194
  ) -> list[Diagnostic]:
@@ -156,11 +212,12 @@ def analyze(
156
212
  baseline: Path | None = None,
157
213
  root: Path | None = None,
158
214
  jobs: int = 1,
215
+ sources: SourceCatalog | None = None,
159
216
  ) -> list[Diagnostic]:
160
217
  if jobs < 1:
161
218
  msg = "jobs must be at least 1"
162
219
  raise ValueError(msg)
163
- diagnostics = _check(rule_ids, paths, jobs=jobs)
220
+ diagnostics = _check(rule_ids, paths, jobs=jobs, sources=sources)
164
221
  return diagnostics if baseline is None else _apply_baseline(diagnostics, _read_baseline(baseline), root=root)
165
222
 
166
223
 
@@ -9,14 +9,18 @@ from sarj_python_lint.rules._nominal_project import NominalProjectFacts
9
9
 
10
10
 
11
11
  if TYPE_CHECKING:
12
+ from sarj_python_lint._prepared_sources import PreparedSourceInputs
12
13
  from sarj_python_lint.rules._project_index import ProjectIndexSet
13
14
 
14
15
 
15
16
  @final
16
17
  class AnalysisSession:
17
- def __init__(self) -> None:
18
+ def __init__(self, *, source_inputs: PreparedSourceInputs | None = None) -> None:
18
19
  self.project: ProjectIndexSet | None = None
20
+ self.source_inputs = source_inputs
19
21
  self.first_party = FirstPartyFacts()
20
- self.local_source = LocalSourceFacts()
22
+ self.local_source = (
23
+ LocalSourceFacts() if source_inputs is None else LocalSourceFacts(source_inputs=source_inputs)
24
+ )
21
25
  self.python_target = PythonTargetFacts()
22
26
  self.nominal_project = NominalProjectFacts()
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from typing import TYPE_CHECKING
8
8
 
9
9
  from sarj_python_lint._analysis_session import AnalysisSession
10
+ from sarj_python_lint._prepared_sources import NeedsSerialSourceError, PreparedSourceInputs
10
11
  from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule
11
12
  from sarj_python_lint.rules._local_source import LOCAL_SOURCE_CACHE_LIMIT
12
13
  from sarj_python_lint.rules._paths import clear_path_caches
@@ -14,6 +15,7 @@ from sarj_python_lint.rules._project_index import ProjectIndexSet
14
15
 
15
16
 
16
17
  if TYPE_CHECKING:
18
+ from sarj_python_lint._prepared_sources import PreparedSourceSnapshot
17
19
  from sarj_python_lint.rules._project_index import ProjectSourceSnapshot
18
20
 
19
21
 
@@ -26,6 +28,7 @@ class _WorkerState:
26
28
  session: AnalysisSession
27
29
  resolutions: frozenset[_ResolutionKey] = frozenset()
28
30
  modules: frozenset[Path] = frozenset()
31
+ needs_serial: bool = False
29
32
 
30
33
 
31
34
  @dataclass(frozen=True, slots=True)
@@ -33,70 +36,128 @@ class _FileResult:
33
36
  diagnostics: list[Diagnostic]
34
37
  resolutions: frozenset[_ResolutionKey]
35
38
  modules: frozenset[Path]
39
+ needs_serial: bool = False
36
40
 
37
41
 
38
42
  _state: _WorkerState | None = None
39
43
 
40
44
 
41
- def _initialize(rule_types: list[type[Rule]], snapshot: ProjectSourceSnapshot | None) -> None:
45
+ def _initialize(
46
+ rule_types: list[type[Rule]],
47
+ snapshot: ProjectSourceSnapshot | None,
48
+ source_snapshot: PreparedSourceSnapshot | None = None,
49
+ ) -> None:
42
50
  global _state # ruff: ignore[global-statement] -- each spawned process owns one initialized session.
43
51
  rules = [rule_type() for rule_type in rule_types]
44
- session = AnalysisSession()
45
- for rule in rules:
46
- rule.prepare_session(session)
52
+ source_inputs = None if source_snapshot is None else PreparedSourceInputs.from_source_snapshot(source_snapshot)
53
+ session = AnalysisSession() if source_inputs is None else AnalysisSession(source_inputs=source_inputs)
54
+ _state = _WorkerState(rules, session)
55
+ try:
56
+ _prepare_worker(_state, snapshot, source_inputs=source_inputs)
57
+ except NeedsSerialSourceError:
58
+ _state.needs_serial = True
59
+
60
+
61
+ def _prepare_worker(
62
+ state: _WorkerState, snapshot: ProjectSourceSnapshot | None, *, source_inputs: PreparedSourceInputs | None
63
+ ) -> None:
64
+ for rule in state.rules:
65
+ rule.prepare_session(state.session)
47
66
  clear_path_caches()
48
- project = ProjectIndexSet.from_source_snapshot(snapshot) if snapshot is not None else None
49
- session.project = project
50
- for rule in rules:
67
+ project = None
68
+ if snapshot is not None:
69
+ project = (
70
+ ProjectIndexSet.from_source_snapshot(snapshot)
71
+ if source_inputs is None
72
+ else ProjectIndexSet.from_source_snapshot(snapshot, sealed=True)
73
+ )
74
+ state.session.project = project
75
+ for rule in state.rules:
51
76
  if isinstance(rule, ProjectRule) and project is not None:
52
77
  rule.prepare(project)
53
- _state = _WorkerState(rules, session)
54
78
 
55
79
 
56
- def _check_file(item: tuple[Path, str]) -> _FileResult:
80
+ def _check_file(item: tuple[Path, str | None]) -> _FileResult:
57
81
  from sarj_python_lint.__main__ import check_source # ruff: ignore[import-outside-top-level] -- keep spawn targets in an importable module.
58
82
 
59
83
  if _state is None:
60
84
  msg = "Python analysis worker was not initialized"
61
85
  raise RuntimeError(msg)
62
86
  path, source = item
63
- diagnostics = check_source(_state.rules, path, source, session=_state.session)
87
+ if _state.needs_serial:
88
+ return _FileResult([], frozenset(), frozenset(), needs_serial=True)
89
+ try:
90
+ if source is None:
91
+ source = _selected_worker_source(_state.session, path)
92
+ diagnostics = check_source(_state.rules, path, source, session=_state.session)
93
+ except NeedsSerialSourceError:
94
+ _state.needs_serial = True
95
+ return _FileResult([], frozenset(), frozenset(), needs_serial=True)
64
96
  keys = _state.session.local_source.cache_keys()
65
97
  result = _FileResult(diagnostics, keys.resolutions - _state.resolutions, keys.modules - _state.modules)
66
98
  _state.resolutions, _state.modules = keys.resolutions, keys.modules
67
99
  return result
68
100
 
69
101
 
102
+ def _selected_worker_source(session: AnalysisSession, path: Path) -> str:
103
+ if session.source_inputs is None:
104
+ raise NeedsSerialSourceError
105
+ return session.source_inputs.selected_text(path)
106
+
107
+
70
108
  def check_parallel(
71
109
  rule_types: list[type[Rule]],
72
110
  loaded: dict[Path, str],
73
111
  project: ProjectIndexSet | None,
74
112
  *,
75
113
  jobs: int,
114
+ source_inputs: PreparedSourceInputs | None = None,
76
115
  ) -> list[Diagnostic] | None:
77
116
  # The untouched parent rules/session remain the serial fallback. Workers
78
117
  # receive the same complete project snapshot and selected source strings.
118
+ source_snapshot = None if source_inputs is None else source_inputs.source_snapshot()
119
+ if source_snapshot is not None and source_snapshot.eligible_raw_count >= LOCAL_SOURCE_CACHE_LIMIT:
120
+ return None
79
121
  workers = min(jobs, 2, len(loaded))
80
122
  pool = ProcessPoolExecutor(
81
123
  max_workers=workers,
82
124
  mp_context=multiprocessing.get_context("spawn"),
83
125
  initializer=_initialize,
84
- initargs=(rule_types, project.source_snapshot() if project is not None else None),
126
+ initargs=(rule_types, project.source_snapshot() if project is not None else None, source_snapshot),
85
127
  )
86
128
  diagnostics: list[Diagnostic] = []
87
129
  resolutions: set[_ResolutionKey] = set()
88
130
  modules: set[Path] = set()
131
+ needs_serial = False
89
132
  try:
90
133
  # Keep workers busy while an earlier file delays ordered results.
91
- for result in pool.map(_check_file, loaded.items(), chunksize=1, buffersize=workers * 4):
134
+ items = (
135
+ (
136
+ path,
137
+ None
138
+ if source_inputs is not None
139
+ and (captured := source_inputs.selected_capture(path)) is not None
140
+ and captured.snapshot is not None
141
+ else source,
142
+ )
143
+ for path, source in loaded.items()
144
+ )
145
+ for result in pool.map(_check_file, items, chunksize=1, buffersize=workers * 4):
146
+ if result.needs_serial:
147
+ # Drain the actual worker results so another initializer/file
148
+ # failure cannot be disguised as a clean serial replay.
149
+ needs_serial = True
150
+ continue
92
151
  resolutions.update(result.resolutions)
93
152
  modules.update(result.modules)
94
153
  # A worker cannot hit either order-dependent limit when the whole
95
154
  # union stays strictly below it. At the boundary, discard all output.
96
- if len(resolutions) >= LOCAL_SOURCE_CACHE_LIMIT or len(modules) >= LOCAL_SOURCE_CACHE_LIMIT:
155
+ if not needs_serial and (
156
+ len(resolutions) >= LOCAL_SOURCE_CACHE_LIMIT or len(modules) >= LOCAL_SOURCE_CACHE_LIMIT
157
+ ):
97
158
  return None
98
159
  diagnostics.extend(result.diagnostics)
99
- return diagnostics
160
+ return None if needs_serial else diagnostics
100
161
  finally:
101
162
  # Bound queued work and reap before fallback/error return. Running file
102
163
  # checks finish normally; group cancellation belongs to the supervisor.
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, final
6
+
7
+ from sarj_rule_contracts import SourceSnapshot
8
+
9
+ from sarj_python_lint._source import decode_python_source
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from sarj_rule_contracts import SourceCatalog
14
+
15
+
16
+ LOCAL_MODULE_MAX_BYTES = 256_000
17
+ type ResolutionKey = tuple[Path, str | None, int]
18
+
19
+
20
+ # The sealed worker needs an observation available only to the parent.
21
+ class NeedsSerialSourceError(Exception):
22
+ pass
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class CapturedSource:
27
+ # Larger project units keep their text and actual byte size on the wire;
28
+ # local analysis must decline them without a second full raw body.
29
+ path: Path
30
+ raw_size: int
31
+ snapshot: SourceSnapshot | None = field(default=None, repr=False)
32
+
33
+ @classmethod
34
+ def from_snapshot(cls, snapshot: SourceSnapshot) -> CapturedSource:
35
+ size = len(snapshot.raw)
36
+ return cls(snapshot.path, size, snapshot if size <= LOCAL_MODULE_MAX_BYTES else None)
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class PreparedSourceSnapshot:
41
+ entries: tuple[tuple[Path, CapturedSource], ...]
42
+ selected: tuple[tuple[Path, CapturedSource], ...]
43
+ conflicts: frozenset[Path]
44
+ resolutions: tuple[tuple[ResolutionKey, Path | None], ...]
45
+ failed_modules: frozenset[Path]
46
+
47
+ @property
48
+ def eligible_raw_count(self) -> int:
49
+ return len({id(entry.snapshot) for _, entry in (*self.entries, *self.selected) if entry.snapshot is not None})
50
+
51
+ @property
52
+ def raw_bytes(self) -> int:
53
+ snapshots = {
54
+ id(entry.snapshot): entry.snapshot
55
+ for _, entry in (*self.entries, *self.selected)
56
+ if entry.snapshot is not None
57
+ }
58
+ return sum(len(snapshot.raw) for snapshot in snapshots.values())
59
+
60
+
61
+ @final
62
+ class PreparedSourceInputs:
63
+ def __init__(self, catalog: SourceCatalog | None = None, *, sealed: bool = False) -> None:
64
+ self._catalog = catalog
65
+ self.sealed = sealed
66
+ self._entries: dict[Path, CapturedSource] = {}
67
+ self._selected: dict[Path, CapturedSource] = {}
68
+ self._conflicts: set[Path] = set()
69
+ self._resolutions: dict[ResolutionKey, Path | None] = {}
70
+ self._failed_modules: set[Path] = set()
71
+
72
+ def remember_selected(self, path: Path, snapshot: SourceSnapshot) -> None:
73
+ captured = CapturedSource.from_snapshot(snapshot)
74
+ self._selected[path] = captured
75
+ self.remember(path, captured)
76
+
77
+ def selected_capture(self, path: Path) -> CapturedSource | None:
78
+ return self._selected.get(path)
79
+
80
+ def selected_text(self, path: Path) -> str:
81
+ captured = self._selected.get(path)
82
+ if captured is None or captured.snapshot is None:
83
+ raise NeedsSerialSourceError
84
+ try:
85
+ return decode_python_source(captured.snapshot)
86
+ except UnicodeError, SyntaxError:
87
+ raise NeedsSerialSourceError from None
88
+
89
+ def remember(self, path: Path, captured: CapturedSource) -> None:
90
+ if self.sealed:
91
+ raise NeedsSerialSourceError
92
+ previous = self._entries.get(path)
93
+ if previous is None:
94
+ self._entries[path] = captured
95
+ elif previous is not captured and not self._same_bytes(previous, captured):
96
+ self._conflicts.add(path)
97
+
98
+ def _same_bytes(self, first: CapturedSource, second: CapturedSource) -> bool:
99
+ left = self._body(first)
100
+ right = self._body(second)
101
+ return left is not None and right is not None and left.raw == right.raw
102
+
103
+ def _body(self, captured: CapturedSource) -> SourceSnapshot | None:
104
+ if captured.snapshot is not None:
105
+ return captured.snapshot
106
+ return None if self._catalog is None else self._catalog.get(captured.path)
107
+
108
+ def associate_selected(self, original: Path, prepared: Path) -> CapturedSource | None:
109
+ captured = self._selected.get(original)
110
+ if captured is not None:
111
+ self.remember(prepared, captured)
112
+ return captured
113
+
114
+ def project_snapshot(self, path: Path) -> SourceSnapshot | None:
115
+ if path in self._conflicts:
116
+ return None
117
+ captured = self._entries.get(path)
118
+ return None if captured is None else self._body(captured)
119
+
120
+ def module_snapshot(self, path: Path) -> SourceSnapshot | None:
121
+ if path not in self._conflicts:
122
+ captured = self._entries.get(path)
123
+ if captured is not None:
124
+ if captured.raw_size > LOCAL_MODULE_MAX_BYTES:
125
+ return None
126
+ if captured.snapshot is not None:
127
+ return captured.snapshot
128
+ if path in self._failed_modules:
129
+ return None
130
+ if self.sealed:
131
+ raise NeedsSerialSourceError
132
+ # Unknown/conflicting byte views retain the parent's legacy bounded read.
133
+ with path.open("rb") as stream:
134
+ raw = stream.read(LOCAL_MODULE_MAX_BYTES + 1)
135
+ if len(raw) > LOCAL_MODULE_MAX_BYTES:
136
+ return None
137
+ return SourceSnapshot(path, raw)
138
+
139
+ def remember_module(self, path: Path, snapshot: SourceSnapshot) -> None:
140
+ # Called only after the local decoder and parser have admitted a module.
141
+ if not self.sealed and path not in self._conflicts:
142
+ self.remember(path, CapturedSource.from_snapshot(snapshot))
143
+
144
+ def record_failure(self, path: Path) -> None:
145
+ if not self.sealed and path not in self._entries and path not in self._conflicts:
146
+ self._failed_modules.add(path)
147
+
148
+ def resolution(self, key: ResolutionKey) -> Path | None:
149
+ if key not in self._resolutions:
150
+ raise NeedsSerialSourceError
151
+ return self._resolutions[key]
152
+
153
+ def record_resolution(self, key: ResolutionKey, result: Path | None) -> None:
154
+ if self.sealed:
155
+ raise NeedsSerialSourceError
156
+ self._resolutions[key] = result
157
+
158
+ def source_snapshot(self) -> PreparedSourceSnapshot:
159
+ return PreparedSourceSnapshot(
160
+ tuple(self._entries.items()),
161
+ tuple(self._selected.items()),
162
+ frozenset(self._conflicts),
163
+ tuple(self._resolutions.items()),
164
+ frozenset(self._failed_modules),
165
+ )
166
+
167
+ @classmethod
168
+ def from_source_snapshot(cls, snapshot: PreparedSourceSnapshot) -> PreparedSourceInputs:
169
+ inputs = cls(sealed=True)
170
+ inputs._entries = dict(snapshot.entries)
171
+ inputs._selected = dict(snapshot.selected)
172
+ inputs._conflicts = set(snapshot.conflicts)
173
+ inputs._resolutions = dict(snapshot.resolutions)
174
+ inputs._failed_modules = set(snapshot.failed_modules)
175
+ return inputs
@@ -6,18 +6,26 @@ import tokenize
6
6
  from typing import TYPE_CHECKING
7
7
  import unicodedata
8
8
 
9
+ from sarj_rule_contracts import NewlinePolicy, SourceSnapshot
10
+
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  from pathlib import Path
12
14
 
15
+ from sarj_rule_contracts import SourceCatalog
16
+
13
17
 
14
18
  def python_source_encoding(data: bytes) -> str:
15
19
  return tokenize.detect_encoding(BytesIO(data).readline)[0]
16
20
 
17
21
 
18
- def read_python_source(path: Path) -> str:
19
- with tokenize.open(path) as stream:
20
- return stream.read()
22
+ def read_python_source(path: Path, *, sources: SourceCatalog | None = None) -> str:
23
+ snapshot = SourceSnapshot.read(path) if sources is None else sources.capture(path)
24
+ return decode_python_source(snapshot)
25
+
26
+
27
+ def decode_python_source(snapshot: SourceSnapshot) -> str:
28
+ return snapshot.decode(python_source_encoding(snapshot.raw), newlines=NewlinePolicy.UNIVERSAL).text
21
29
 
22
30
 
23
31
  def python_source_lines(source: str) -> list[str]:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import asdict, dataclass
3
+ from dataclasses import asdict, dataclass, field
4
4
  import hashlib
5
5
  import json
6
6
  from os.path import commonpath
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  from statistics import median
9
9
  import sys
10
10
  from time import perf_counter
11
- from typing import TYPE_CHECKING, Annotated
11
+ from typing import TYPE_CHECKING, Annotated, Literal
12
12
 
13
13
  from sarj_rule_contracts import RuleId
14
14
  import typer
@@ -21,12 +21,21 @@ if TYPE_CHECKING:
21
21
  from collections.abc import Sequence
22
22
 
23
23
 
24
+ type MemoryScope = Literal["process-lifetime-self", "unsupported"]
25
+
26
+
24
27
  @dataclass(frozen=True, slots=True)
25
28
  class BenchmarkSample:
26
29
  seconds: float
27
30
  diagnostics: int
28
31
  diagnostic_digest: str
29
32
  peak_memory_bytes: int | None
33
+ memory_scope: MemoryScope = field(init=False)
34
+
35
+ def __post_init__(self) -> None:
36
+ object.__setattr__(
37
+ self, "memory_scope", "unsupported" if self.peak_memory_bytes is None else "process-lifetime-self"
38
+ )
30
39
 
31
40
 
32
41
  @dataclass(frozen=True, slots=True)
@@ -40,6 +49,8 @@ class BenchmarkReport:
40
49
 
41
50
 
42
51
  def peak_memory_bytes() -> int | None:
52
+ if sys.platform not in {"linux", "darwin"}:
53
+ return None
43
54
  try:
44
55
  import resource # ruff: ignore[import-outside-top-level] — optional Unix-only memory instrumentation.
45
56
  except ImportError:
@@ -98,7 +109,8 @@ def measure(
98
109
  ).encode()
99
110
  )
100
111
  digest.update(b"\n")
101
- samples.append(BenchmarkSample(elapsed, len(diagnostics), digest.hexdigest(), peak_memory_bytes()))
112
+ peak = peak_memory_bytes()
113
+ samples.append(BenchmarkSample(elapsed, len(diagnostics), digest.hexdigest(), peak))
102
114
  if len({sample.diagnostic_digest for sample in samples}) != 1:
103
115
  msg = "diagnostics changed between benchmark repetitions"
104
116
  raise RuntimeError(msg)
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, ClassVar, Final
10
10
 
11
11
  from sarj_rule_contracts import (
12
12
  AutofixPolicy as AutofixPolicy,
13
+ DiagnosticSeverity as DiagnosticSeverity,
13
14
  ExampleFile as ExampleFile,
14
15
  ExampleOutcome as ExampleOutcome,
15
16
  NativeRuleSpec as NativeRuleSpec,
@@ -17,6 +18,7 @@ from sarj_rule_contracts import (
17
18
  RuleDocumentation as RuleDocumentation,
18
19
  RuleExample as RuleExample,
19
20
  Severity as Severity,
21
+ diagnostic_severity,
20
22
  )
21
23
 
22
24
  from sarj_python_lint._python_target import PythonTargetFacts
@@ -100,10 +102,13 @@ class Diagnostic:
100
102
  col: int
101
103
  code: str
102
104
  message: str
103
- severity: Severity = Severity.ERROR
105
+ severity: DiagnosticSeverity = Severity.ERROR
104
106
  column_encoding: ColumnEncoding = ColumnEncoding.UTF8_BYTES
105
107
  fixes: tuple[Fix, ...] = ()
106
108
 
109
+ def __post_init__(self) -> None:
110
+ diagnostic_severity(self.severity)
111
+
107
112
  def format(self) -> str:
108
113
  label = "warning: " if self.severity is Severity.WARNING else ""
109
114
  return f"{self.path}:{self.line}:{self.col}: {self.code} {label}{self.message}"