sarj-python-lint 1.68.0__tar.gz → 1.69.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 (250) hide show
  1. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/__main__.py +19 -5
  4. sarj_python_lint-1.69.0/src/sarj_python_lint/_parallel.py +102 -0
  5. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_local_source.py +13 -3
  6. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_project_index.py +38 -0
  7. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/.gitignore +0 -0
  8. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/LICENSE +0 -0
  9. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/README.md +0 -0
  10. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/__init__.py +0 -0
  11. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_python_target.py +0 -0
  15. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  16. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_secret_names.py +0 -0
  17. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_source.py +0 -0
  18. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/_version.py +0 -0
  19. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/benchmark.py +0 -0
  20. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/cast_access.py +0 -0
  21. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
  22. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/json_boundary.py +0 -0
  23. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/py.typed +0 -0
  24. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/ratchet.py +0 -0
  25. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rule_base.py +0 -0
  26. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  27. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  28. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  29. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  30. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  31. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  32. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  33. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  34. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  35. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  36. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  37. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  38. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  39. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_exports.py +0 -0
  40. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  41. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  42. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  43. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  44. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  45. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  46. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  47. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  48. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  49. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  50. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  51. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  52. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  53. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  54. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  55. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  56. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  57. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  58. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  59. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  60. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  61. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  62. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  63. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  64. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  65. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  66. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  67. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  68. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  69. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  70. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  71. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  72. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  73. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  74. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  75. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  76. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  77. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  78. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  79. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  80. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  81. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  82. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  83. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  84. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  85. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  86. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  87. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  88. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  89. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  90. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  91. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  92. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  93. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  94. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  95. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  96. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  97. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  98. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  99. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  100. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  101. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  102. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  103. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  104. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  105. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  106. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  107. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  108. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  109. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  110. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  111. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  112. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  113. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  114. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  115. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  116. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  117. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  118. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  119. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  120. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  121. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  122. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  123. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  124. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  125. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  126. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  127. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  128. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  129. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  130. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  131. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  132. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  133. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  134. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  135. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  136. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  137. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  138. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  139. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  140. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  141. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  142. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  143. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  144. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  145. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  146. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  147. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  148. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  149. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  150. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  151. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  152. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  153. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  154. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  155. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  156. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  157. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  158. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  159. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  160. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  161. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  162. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  163. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  164. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  165. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  166. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  167. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  168. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  169. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  170. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  171. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  172. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  173. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  174. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  175. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  176. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  177. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  178. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  179. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  180. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  181. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  182. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  183. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  184. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  185. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  186. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  187. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  188. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  189. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  190. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  191. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  192. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  193. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  194. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  195. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  196. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  197. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  198. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  199. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  200. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  201. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  202. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  203. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  204. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  205. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  206. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  207. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  208. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  209. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  210. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  211. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  212. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  213. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  214. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  215. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  216. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  217. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  218. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  219. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  220. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  221. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  222. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  223. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  224. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  225. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  226. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  227. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  228. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  229. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  230. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  231. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  232. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  233. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  234. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  235. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  236. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  237. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  238. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  239. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  240. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  241. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  242. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  243. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  244. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  245. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  246. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  247. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  248. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  249. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  250. {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.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.68.0
3
+ Version: 1.69.0
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "1.68.0"
3
+ version = "1.69.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" }]
@@ -96,7 +96,7 @@ def _canonical_rule_ids(rule_ids: list[str]) -> list[str]:
96
96
  return list(dict.fromkeys(rid if rid in REGISTRY else aliases.get(rid, rid) for rid in rule_ids))
97
97
 
98
98
 
99
- def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
99
+ def _check(rule_ids: list[str], paths: list[Path], *, jobs: int = 1) -> list[Diagnostic]:
100
100
  rule_ids = _canonical_rule_ids(rule_ids)
101
101
  unknown = [rid for rid in rule_ids if rid not in REGISTRY]
102
102
  if unknown:
@@ -116,6 +116,12 @@ def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
116
116
  session.project = indexes
117
117
  for rule in project_rules:
118
118
  rule.prepare(indexes)
119
+ if jobs > 1 and len(loaded) > 1:
120
+ from sarj_python_lint._parallel import check_parallel # ruff: ignore[import-outside-top-level] -- serial API users do not need multiprocessing.
121
+
122
+ parallel = check_parallel([type(rule) for rule in rules], loaded, session.project, jobs=jobs)
123
+ if parallel is not None:
124
+ return parallel
119
125
  diags: list[Diagnostic] = []
120
126
  for p, source in loaded.items():
121
127
  diags.extend(check_source(rules, p, source, session=session))
@@ -144,8 +150,12 @@ def analyze(
144
150
  *,
145
151
  baseline: Path | None = None,
146
152
  root: Path | None = None,
153
+ jobs: int = 1,
147
154
  ) -> list[Diagnostic]:
148
- diagnostics = _check(rule_ids, paths)
155
+ if jobs < 1:
156
+ msg = "jobs must be at least 1"
157
+ raise ValueError(msg)
158
+ diagnostics = _check(rule_ids, paths, jobs=jobs)
149
159
  return diagnostics if baseline is None else _apply_baseline(diagnostics, _read_baseline(baseline), root=root)
150
160
 
151
161
 
@@ -481,9 +491,11 @@ def _apply_baseline(
481
491
  return out
482
492
 
483
493
 
484
- def _run_check(rule: list[str], files: list[Path], baseline: Path | None, update_baseline: Path | None) -> int:
494
+ def _run_check(
495
+ rule: list[str], files: list[Path], baseline: Path | None, update_baseline: Path | None, *, jobs: int = 1
496
+ ) -> int:
485
497
  try:
486
- diags = analyze(rule, files)
498
+ diags = analyze(rule, files, jobs=jobs)
487
499
  except (OSError, ValueError, SyntaxError) as exc:
488
500
  sys.stderr.write(f"error: {exc}\n")
489
501
  return 2
@@ -554,14 +566,16 @@ def _check_command(
554
566
  context: typer.Context,
555
567
  files: Annotated[list[Path], typer.Argument()],
556
568
  rule: Annotated[list[str], typer.Option("--rule", help="Rule ID (repeat for multiple).")],
569
+ *,
557
570
  baseline: Annotated[
558
571
  Path | None, typer.Option(help="Per-file shrink-only baseline JSON: {path: {CODE: count}}.")
559
572
  ] = None,
560
573
  update_baseline: Annotated[
561
574
  Path | None, typer.Option(help="Write current per-file diagnostic counts to JSON and exit 0.")
562
575
  ] = None,
576
+ jobs: Annotated[int, typer.Option(min=1, help="Analysis worker budget (up to 2); default is serial.")] = 1,
563
577
  ) -> None:
564
- context.ensure_object(_Result).code = _run_check(rule, files, baseline, update_baseline)
578
+ context.ensure_object(_Result).code = _run_check(rule, files, baseline, update_baseline, jobs=jobs)
565
579
 
566
580
 
567
581
  def main(argv: list[str] | None = None) -> int:
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from concurrent.futures import ProcessPoolExecutor
4
+ from dataclasses import dataclass
5
+ import multiprocessing
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ from sarj_python_lint._analysis_session import AnalysisSession
10
+ from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule
11
+ from sarj_python_lint.rules._local_source import LOCAL_SOURCE_CACHE_LIMIT
12
+ from sarj_python_lint.rules._paths import clear_path_caches
13
+ from sarj_python_lint.rules._project_index import ProjectIndexSet
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ from sarj_python_lint.rules._project_index import ProjectSourceSnapshot
18
+
19
+
20
+ type _ResolutionKey = tuple[Path, str | None, int]
21
+
22
+
23
+ @dataclass
24
+ class _WorkerState:
25
+ rules: list[Rule]
26
+ session: AnalysisSession
27
+ resolutions: frozenset[_ResolutionKey] = frozenset()
28
+ modules: frozenset[Path] = frozenset()
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class _FileResult:
33
+ diagnostics: list[Diagnostic]
34
+ resolutions: frozenset[_ResolutionKey]
35
+ modules: frozenset[Path]
36
+
37
+
38
+ _state: _WorkerState | None = None
39
+
40
+
41
+ def _initialize(rule_types: list[type[Rule]], snapshot: ProjectSourceSnapshot | None) -> None:
42
+ global _state # ruff: ignore[global-statement] -- each spawned process owns one initialized session.
43
+ rules = [rule_type() for rule_type in rule_types]
44
+ session = AnalysisSession()
45
+ for rule in rules:
46
+ rule.prepare_session(session)
47
+ 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:
51
+ if isinstance(rule, ProjectRule) and project is not None:
52
+ rule.prepare(project)
53
+ _state = _WorkerState(rules, session)
54
+
55
+
56
+ def _check_file(item: tuple[Path, str]) -> _FileResult:
57
+ from sarj_python_lint.__main__ import check_source # ruff: ignore[import-outside-top-level] -- keep spawn targets in an importable module.
58
+
59
+ if _state is None:
60
+ msg = "Python analysis worker was not initialized"
61
+ raise RuntimeError(msg)
62
+ path, source = item
63
+ diagnostics = check_source(_state.rules, path, source, session=_state.session)
64
+ keys = _state.session.local_source.cache_keys()
65
+ result = _FileResult(diagnostics, keys.resolutions - _state.resolutions, keys.modules - _state.modules)
66
+ _state.resolutions, _state.modules = keys.resolutions, keys.modules
67
+ return result
68
+
69
+
70
+ def check_parallel(
71
+ rule_types: list[type[Rule]],
72
+ loaded: dict[Path, str],
73
+ project: ProjectIndexSet | None,
74
+ *,
75
+ jobs: int,
76
+ ) -> list[Diagnostic] | None:
77
+ # The untouched parent rules/session remain the serial fallback. Workers
78
+ # receive the same complete project snapshot and selected source strings.
79
+ workers = min(jobs, 2, len(loaded))
80
+ pool = ProcessPoolExecutor(
81
+ max_workers=workers,
82
+ mp_context=multiprocessing.get_context("spawn"),
83
+ initializer=_initialize,
84
+ initargs=(rule_types, project.source_snapshot() if project is not None else None),
85
+ )
86
+ diagnostics: list[Diagnostic] = []
87
+ resolutions: set[_ResolutionKey] = set()
88
+ modules: set[Path] = set()
89
+ try:
90
+ for result in pool.map(_check_file, loaded.items(), chunksize=1, buffersize=workers):
91
+ resolutions.update(result.resolutions)
92
+ modules.update(result.modules)
93
+ # A worker cannot hit either order-dependent limit when the whole
94
+ # union stays strictly below it. At the boundary, discard all output.
95
+ if len(resolutions) >= LOCAL_SOURCE_CACHE_LIMIT or len(modules) >= LOCAL_SOURCE_CACHE_LIMIT:
96
+ return None
97
+ diagnostics.extend(result.diagnostics)
98
+ return diagnostics
99
+ finally:
100
+ # Bound queued work and reap before fallback/error return. Running file
101
+ # checks finish normally; group cancellation belongs to the supervisor.
102
+ pool.shutdown(wait=True, cancel_futures=True)
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
 
16
16
  _MAX_ANCESTORS = 24
17
17
  _MAX_MODULE_BYTES = 256_000
18
- _MAX_MODULES = 512
18
+ LOCAL_SOURCE_CACHE_LIMIT = 512
19
19
 
20
20
 
21
21
  @dataclass(frozen=True, slots=True)
@@ -26,6 +26,12 @@ class LocalModule:
26
26
  runtime_imports: ImportIndex
27
27
 
28
28
 
29
+ @dataclass(frozen=True, slots=True)
30
+ class LocalSourceKeys:
31
+ resolutions: frozenset[tuple[Path, str | None, int]]
32
+ modules: frozenset[Path]
33
+
34
+
29
35
  @final
30
36
  class LocalSourceFacts:
31
37
  def __init__(self) -> None:
@@ -40,7 +46,7 @@ class LocalSourceFacts:
40
46
  key = (current, module, level)
41
47
  if key in self._resolutions:
42
48
  return self._resolutions[key]
43
- if len(self._resolutions) >= _MAX_MODULES:
49
+ if len(self._resolutions) >= LOCAL_SOURCE_CACHE_LIMIT:
44
50
  return None
45
51
  result = _resolve_module(current, module, level, facts=facts)
46
52
  self._resolutions[key] = result
@@ -49,12 +55,16 @@ class LocalSourceFacts:
49
55
  def read_module(self, path: Path) -> LocalModule | None:
50
56
  if path in self._modules:
51
57
  return self._modules[path]
52
- if len(self._modules) >= _MAX_MODULES:
58
+ if len(self._modules) >= LOCAL_SOURCE_CACHE_LIMIT:
53
59
  return None
54
60
  module = _read_module(path)
55
61
  self._modules[path] = module
56
62
  return module
57
63
 
64
+ def cache_keys(self) -> LocalSourceKeys:
65
+ # Failed lookups consume the same semantic budgets as successful ones.
66
+ return LocalSourceKeys(frozenset(self._resolutions), frozenset(self._modules))
67
+
58
68
 
59
69
  def _resolve_module(current: Path, module: str | None, level: int, *, facts: FirstPartyFacts) -> Path | None:
60
70
  if ".." in current.parts or level > _MAX_ANCESTORS:
@@ -154,6 +154,20 @@ class LoadedSource:
154
154
  source: str
155
155
 
156
156
 
157
+ @dataclass(frozen=True, slots=True)
158
+ class _IndexedSource:
159
+ key: Path
160
+ path: Path
161
+ module: str | None
162
+ source: str
163
+
164
+
165
+ @dataclass(frozen=True, slots=True)
166
+ class ProjectSourceSnapshot:
167
+ roots: tuple[Path, ...]
168
+ units: tuple[_IndexedSource, ...]
169
+
170
+
157
171
  @dataclass(frozen=True, slots=True)
158
172
  class _ClassValueUse:
159
173
  reference: ast.Name | ast.Attribute
@@ -196,6 +210,30 @@ class ProjectIndexSet:
196
210
  self._classes = MappingProxyType(classes)
197
211
  self._nominals = MappingProxyType({key: frozenset(value) for key, value in nominals.items()})
198
212
 
213
+ def source_snapshot(self) -> ProjectSourceSnapshot:
214
+ # Rebuild memoized facts without rescanning roots or changing the
215
+ # module identities/order of the complete prepared source graph.
216
+ return ProjectSourceSnapshot(
217
+ self._roots,
218
+ tuple(_IndexedSource(key, unit.path, unit.module, unit.source) for key, unit in self._units.items()),
219
+ )
220
+
221
+ @classmethod
222
+ def from_source_snapshot(cls, snapshot: ProjectSourceSnapshot) -> Self:
223
+ units: dict[Path, SourceUnit] = {}
224
+ for source in snapshot.units:
225
+ tree: ast.Module | None = None
226
+ with suppress(SyntaxError):
227
+ tree = ast.parse(source.source, filename=str(source.path))
228
+ units[source.key] = SourceUnit(
229
+ path=source.path,
230
+ module=source.module,
231
+ source=source.source,
232
+ tree=tree,
233
+ imports=MappingProxyType(_imports(source.module, tree, is_package=source.path.name == "__init__.py")),
234
+ )
235
+ return cls(units, snapshot.roots)
236
+
199
237
  @classmethod
200
238
  def build(
201
239
  cls,