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