sarj-python-lint 1.68.0__tar.gz → 1.69.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/pyproject.toml +1 -1
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/__main__.py +19 -5
- sarj_python_lint-1.69.1/src/sarj_python_lint/_parallel.py +102 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_local_source.py +13 -3
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_project_index.py +38 -0
- sarj_python_lint-1.69.1/src/sarj_python_lint/rules/prefer_direct_import_module.py +113 -0
- sarj_python_lint-1.68.0/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -83
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/.gitignore +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/LICENSE +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/README.md +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/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.
|
|
3
|
+
Version: 1.69.1
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -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
|
-
|
|
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(
|
|
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)
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
|
|
16
16
|
_MAX_ANCESTORS = 24
|
|
17
17
|
_MAX_MODULE_BYTES = 256_000
|
|
18
|
-
|
|
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) >=
|
|
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) >=
|
|
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:
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
@@ -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,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from keyword import iskeyword
|
|
5
|
+
from pathlib import PurePosixPath
|
|
6
|
+
from typing import TYPE_CHECKING, final, override
|
|
7
|
+
from unicodedata import is_normalized
|
|
8
|
+
|
|
9
|
+
from sarj_python_lint.rule_base import (
|
|
10
|
+
AutofixPolicy,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
ExampleFile,
|
|
13
|
+
ExampleOutcome,
|
|
14
|
+
Rule,
|
|
15
|
+
RuleCategory,
|
|
16
|
+
RuleDocumentation,
|
|
17
|
+
RuleExample,
|
|
18
|
+
Severity,
|
|
19
|
+
)
|
|
20
|
+
from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@final
|
|
28
|
+
class PreferDirectImportModule(Rule):
|
|
29
|
+
id = "prefer-direct-import-module"
|
|
30
|
+
code = "SARJ529"
|
|
31
|
+
documentation = RuleDocumentation(
|
|
32
|
+
default_level=Severity.WARNING,
|
|
33
|
+
summary="Prefer static imports for eager module loads with a literal module name.",
|
|
34
|
+
rationale="Static imports expose known dependencies to readers and tooling without dynamic loader indirection.",
|
|
35
|
+
remediation="Replace a fixed eager module load with a module-level import, preserving its binding: plugin = importlib.import_module('plugins.billing') becomes import plugins.billing as plugin. Keep dynamic loading when module selection or import timing requires it.",
|
|
36
|
+
category=RuleCategory.STYLE,
|
|
37
|
+
autofix=AutofixPolicy.NONE,
|
|
38
|
+
limitations=(
|
|
39
|
+
"Checks direct module-level assignments and expression statements calling the proven stdlib import_module function, through qualified or directly imported names, with one literal absolute module name.",
|
|
40
|
+
"Function/class bodies, conditional or guarded imports, variable module names, relative imports, callback references, shadowed or mutated providers and generated code are excluded. Static import syntax must be valid for every module-name segment.",
|
|
41
|
+
"This is a warning without automatic rewriting: moving an import can change execution order, optional-dependency behavior or cycle handling. The rule does not prefer one spelling of genuinely dynamic import_module calls.",
|
|
42
|
+
),
|
|
43
|
+
examples=(
|
|
44
|
+
RuleExample(
|
|
45
|
+
example_id="literal-eager-loader",
|
|
46
|
+
title="A known eager dependency is loaded dynamically",
|
|
47
|
+
outcome=ExampleOutcome.MATCH,
|
|
48
|
+
files=(
|
|
49
|
+
ExampleFile.python(
|
|
50
|
+
"app.py", "import importlib\nplugin = importlib.import_module('plugins.billing')\n"
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
focus_path=PurePosixPath("app.py"),
|
|
54
|
+
expected_count=1,
|
|
55
|
+
public=True,
|
|
56
|
+
),
|
|
57
|
+
RuleExample(
|
|
58
|
+
example_id="static-module-import",
|
|
59
|
+
title="A static import preserves the module binding",
|
|
60
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
61
|
+
files=(ExampleFile.python("app.py", "import plugins.billing as plugin\n"),),
|
|
62
|
+
focus_path=PurePosixPath("app.py"),
|
|
63
|
+
expected_count=0,
|
|
64
|
+
public=True,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
description = documentation.summary
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
72
|
+
if context.generated or context.tree is None or "import_module" not in context.symbol_source:
|
|
73
|
+
return []
|
|
74
|
+
loader_references = {
|
|
75
|
+
reference
|
|
76
|
+
for call in context.nodes(ast.Call)
|
|
77
|
+
if context.imports.resolved_qualified_name(call.func) == "importlib.import_module"
|
|
78
|
+
for reference in ast.walk(call.func)
|
|
79
|
+
}
|
|
80
|
+
if dynamic_namespace_access(
|
|
81
|
+
(node for node in context.nodes(ast.AST) if node not in loader_references),
|
|
82
|
+
context.imports,
|
|
83
|
+
context.parents,
|
|
84
|
+
):
|
|
85
|
+
return []
|
|
86
|
+
return [
|
|
87
|
+
Diagnostic(
|
|
88
|
+
context.path,
|
|
89
|
+
node.lineno,
|
|
90
|
+
node.col_offset + 1,
|
|
91
|
+
self.code,
|
|
92
|
+
f"Use a module-level static import for the known module '{module}'; preserve the binding with 'import {module} as plugin' when needed.",
|
|
93
|
+
severity=Severity.WARNING,
|
|
94
|
+
)
|
|
95
|
+
for statement in context.tree.body
|
|
96
|
+
if isinstance(statement, ast.Assign | ast.AnnAssign | ast.Expr)
|
|
97
|
+
and isinstance(node := statement.value, ast.Call)
|
|
98
|
+
and (module := _literal_module(node)) is not None
|
|
99
|
+
and context.resource_provenance.runtime_import_name(node.func) == "importlib.import_module"
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _literal_module(call: ast.Call) -> str | None:
|
|
104
|
+
match call:
|
|
105
|
+
case ast.Call(args=[ast.Constant(value=str() as module)], keywords=[]):
|
|
106
|
+
pass
|
|
107
|
+
case ast.Call(args=[], keywords=[ast.keyword(arg="name", value=ast.Constant(value=str() as module))]):
|
|
108
|
+
pass
|
|
109
|
+
case _:
|
|
110
|
+
return None
|
|
111
|
+
if not is_normalized("NFKC", module):
|
|
112
|
+
return None
|
|
113
|
+
return module if all(part.isidentifier() and not iskeyword(part) for part in module.split(".")) else None
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
from pathlib import PurePosixPath
|
|
5
|
-
from typing import TYPE_CHECKING, final, override
|
|
6
|
-
|
|
7
|
-
from sarj_python_lint.rule_base import (
|
|
8
|
-
AutofixPolicy,
|
|
9
|
-
Diagnostic,
|
|
10
|
-
ExampleFile,
|
|
11
|
-
ExampleOutcome,
|
|
12
|
-
Rule,
|
|
13
|
-
RuleCategory,
|
|
14
|
-
RuleDocumentation,
|
|
15
|
-
RuleExample,
|
|
16
|
-
Severity,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from sarj_python_lint._file_context import PythonFileContext
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@final
|
|
25
|
-
class PreferDirectImportModule(Rule):
|
|
26
|
-
id = "prefer-direct-import-module"
|
|
27
|
-
code = "SARJ529"
|
|
28
|
-
documentation = RuleDocumentation(
|
|
29
|
-
default_level=Severity.WARNING,
|
|
30
|
-
summary="Import import_module directly instead of accessing importlib.import_module.",
|
|
31
|
-
rationale="A focused import makes the loader dependency explicit and removes repeated module qualification.",
|
|
32
|
-
remediation="Use 'from importlib import import_module' and annotate importer callables with 'Callable[[str], ModuleType]'. Keep intentional module patching behind an explained local exception.",
|
|
33
|
-
category=RuleCategory.STYLE,
|
|
34
|
-
autofix=AutofixPolicy.NONE,
|
|
35
|
-
limitations=(
|
|
36
|
-
"Only provenance-resolved importlib.import_module attribute reads are checked. Other importlib APIs, direct imports, shadowed or mutated namespaces and generated code are preserved. No automatic rewrite because changing the lookup can change monkeypatch behavior.",
|
|
37
|
-
),
|
|
38
|
-
examples=(
|
|
39
|
-
RuleExample(
|
|
40
|
-
example_id="qualified-loader",
|
|
41
|
-
title="The module loader is qualified at each use",
|
|
42
|
-
outcome=ExampleOutcome.MATCH,
|
|
43
|
-
files=(ExampleFile.python("app.py", "import importlib\nloader = importlib.import_module\n"),),
|
|
44
|
-
focus_path=PurePosixPath("app.py"),
|
|
45
|
-
expected_count=1,
|
|
46
|
-
public=True,
|
|
47
|
-
),
|
|
48
|
-
RuleExample(
|
|
49
|
-
example_id="focused-loader",
|
|
50
|
-
title="Import only the loader",
|
|
51
|
-
outcome=ExampleOutcome.NO_MATCH,
|
|
52
|
-
files=(ExampleFile.python("app.py", "from importlib import import_module\nloader = import_module\n"),),
|
|
53
|
-
focus_path=PurePosixPath("app.py"),
|
|
54
|
-
expected_count=0,
|
|
55
|
-
public=True,
|
|
56
|
-
),
|
|
57
|
-
),
|
|
58
|
-
)
|
|
59
|
-
description = documentation.summary
|
|
60
|
-
|
|
61
|
-
@override
|
|
62
|
-
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
63
|
-
if context.generated or context.tree is None or "import_module" not in context.symbol_source:
|
|
64
|
-
return []
|
|
65
|
-
provenance = context.static_resource_provenance
|
|
66
|
-
return [
|
|
67
|
-
Diagnostic(
|
|
68
|
-
context.path,
|
|
69
|
-
node.lineno,
|
|
70
|
-
node.col_offset + 1,
|
|
71
|
-
self.code,
|
|
72
|
-
"Use 'from importlib import import_module' and call 'import_module(...)' directly.",
|
|
73
|
-
severity=Severity.WARNING,
|
|
74
|
-
)
|
|
75
|
-
for node in context.nodes(ast.Attribute)
|
|
76
|
-
if node.attr == "import_module"
|
|
77
|
-
and isinstance(node.ctx, ast.Load)
|
|
78
|
-
and context.imports.resolved_qualified_name(node) == "importlib.import_module"
|
|
79
|
-
and isinstance(node.value, ast.Name)
|
|
80
|
-
and isinstance(provenance.binding(node.value.id, node), ast.alias)
|
|
81
|
-
and node.value.id not in provenance.mutated
|
|
82
|
-
and not provenance.wildcard
|
|
83
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_names.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_route_shapes.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_runtime_bindings.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_store_writes.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.68.0 → sarj_python_lint-1.69.1}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|