sarj-python-lint 1.77.0__tar.gz → 1.77.3__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.77.0 → sarj_python_lint-1.77.3}/PKG-INFO +1 -1
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/pyproject.toml +1 -1
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/__init__.py +4 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_file_context.py +2 -2
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_fixes.py +3 -1
- sarj_python_lint-1.77.3/src/sarj_python_lint/application_logging.py +379 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_copy_on_write.py +13 -2
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_fastapi.py +136 -44
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_provenance.py +13 -2
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +2 -1
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/.gitignore +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/LICENSE +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/README.md +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/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.77.
|
|
3
|
+
Version: 1.77.3
|
|
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/
|
|
@@ -3,3 +3,7 @@ from sarj_python_lint._fixes import apply_fixes as apply_fixes, python_source_ha
|
|
|
3
3
|
from sarj_python_lint._ratchet_cli import run_ratchet as run_ratchet
|
|
4
4
|
from sarj_python_lint._source import python_source_encoding as python_source_encoding
|
|
5
5
|
from sarj_python_lint._version import __version__ as __version__
|
|
6
|
+
from sarj_python_lint.application_logging import (
|
|
7
|
+
ApplicationLoggingUse as ApplicationLoggingUse,
|
|
8
|
+
application_logging_uses as application_logging_uses,
|
|
9
|
+
)
|
|
@@ -109,7 +109,7 @@ class PythonFileContext:
|
|
|
109
109
|
def test_provenance(self) -> TestProvenance:
|
|
110
110
|
from sarj_python_lint.rules._test_provenance import TestProvenance # ruff: ignore[import-outside-top-level] — defer test analysis until requested
|
|
111
111
|
|
|
112
|
-
return TestProvenance(self)
|
|
112
|
+
return TestProvenance(self, borrow_context=True)
|
|
113
113
|
|
|
114
114
|
def _valid_tree(self) -> ast.Module:
|
|
115
115
|
tree = self.tree
|
|
@@ -122,7 +122,7 @@ class PythonFileContext:
|
|
|
122
122
|
def copy_on_write(self) -> CopyOnWriteAnalysis:
|
|
123
123
|
from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis # ruff: ignore[import-outside-top-level] — share ownership analysis only when requested
|
|
124
124
|
|
|
125
|
-
return CopyOnWriteAnalysis(self)
|
|
125
|
+
return CopyOnWriteAnalysis(self, borrow_context=True)
|
|
126
126
|
|
|
127
127
|
@cached_property
|
|
128
128
|
def resource_provenance(self) -> ResourceProvenance:
|
|
@@ -54,12 +54,14 @@ def apply_fixes(source: str, fixes: Sequence[Fix]) -> str:
|
|
|
54
54
|
edits.sort()
|
|
55
55
|
result: list[bytes] = []
|
|
56
56
|
previous = 0
|
|
57
|
+
previous_start: int | None = None
|
|
57
58
|
for start, end, replacement in edits:
|
|
58
|
-
if start < previous:
|
|
59
|
+
if start < previous or start == previous_start:
|
|
59
60
|
msg = "fix groups overlap"
|
|
60
61
|
raise ValueError(msg)
|
|
61
62
|
result.extend((raw[previous:start], replacement))
|
|
62
63
|
previous = end
|
|
64
|
+
previous_start = start
|
|
63
65
|
result.append(raw[previous:])
|
|
64
66
|
fixed = b"".join(result).decode("utf-8")
|
|
65
67
|
tree = ast.parse(fixed)
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_PUBLIC_EMITTERS = frozenset({"debug", "info", "warning", "warn", "error", "exception", "critical", "fatal", "log"})
|
|
15
|
+
_MODULE_EMITTERS = frozenset("logging." + name for name in _PUBLIC_EMITTERS)
|
|
16
|
+
_LOGGER_FACTORIES = frozenset({"logging.getLogger", "logging.Logger"})
|
|
17
|
+
_BUILTIN_SINKS = frozenset({"logging.StreamHandler", "logging.FileHandler"})
|
|
18
|
+
_ROUTING_ATTRIBUTES = frozenset({"handlers", "filters", "level", "disabled", "propagate", "name"})
|
|
19
|
+
_ROUTING_METHODS = frozenset(
|
|
20
|
+
{
|
|
21
|
+
"setLevel",
|
|
22
|
+
"addHandler",
|
|
23
|
+
"removeHandler",
|
|
24
|
+
"addFilter",
|
|
25
|
+
"removeFilter",
|
|
26
|
+
"isEnabledFor",
|
|
27
|
+
"getEffectiveLevel",
|
|
28
|
+
"hasHandlers",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
_SCOPES = (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)
|
|
32
|
+
_MAX_SOURCE_CHARACTERS = 500_000
|
|
33
|
+
_MIN_ATTRIBUTE_MUTATION_ARGS = 2
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ApplicationLoggingUse:
|
|
38
|
+
line: int
|
|
39
|
+
column: int
|
|
40
|
+
operation: str
|
|
41
|
+
kind: Literal["event", "sink"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class _ModuleImportState:
|
|
46
|
+
import_lines: dict[str, int]
|
|
47
|
+
star_import_line: int
|
|
48
|
+
module_names: frozenset[str]
|
|
49
|
+
root_names: frozenset[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, slots=True)
|
|
53
|
+
class _LoggerBinding:
|
|
54
|
+
scope: ast.AST
|
|
55
|
+
name: str
|
|
56
|
+
assignment: ast.Assign | ast.AnnAssign
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def application_logging_uses(path: Path, source: str, *, root: Path) -> tuple[ApplicationLoggingUse, ...]:
|
|
60
|
+
from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- load native AST helpers only when library policy selects Python source.
|
|
61
|
+
from sarj_python_lint.rules._paths import is_test_path # ruff: ignore[import-outside-top-level] -- preserve the engine-owned test scope without cross-package private imports.
|
|
62
|
+
|
|
63
|
+
if not path.is_absolute() or not root.is_absolute() or ".." in path.parts or ".." in root.parts:
|
|
64
|
+
msg = "logging policy requires canonical absolute source and root paths"
|
|
65
|
+
raise ValueError(msg)
|
|
66
|
+
try:
|
|
67
|
+
relative = path.relative_to(root)
|
|
68
|
+
except ValueError as exc:
|
|
69
|
+
msg = "logging policy source escapes repository root"
|
|
70
|
+
raise ValueError(msg) from exc
|
|
71
|
+
if relative.suffix not in {".py", ".pyi"} or len(source) > _MAX_SOURCE_CHARACTERS:
|
|
72
|
+
msg = "logging policy requires a bounded selected Python source"
|
|
73
|
+
raise ValueError(msg)
|
|
74
|
+
if relative.suffix == ".pyi":
|
|
75
|
+
return ()
|
|
76
|
+
context = PythonFileContext(path, source)
|
|
77
|
+
if is_test_path(relative) or context.generated:
|
|
78
|
+
return ()
|
|
79
|
+
if context.tree is None:
|
|
80
|
+
msg = f"selected Python source could not be parsed: {path}"
|
|
81
|
+
raise ValueError(msg)
|
|
82
|
+
return _LoggingProvenance(context).uses()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class _LoggingProvenance:
|
|
86
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
87
|
+
self.context = context
|
|
88
|
+
tree = context.tree
|
|
89
|
+
if tree is None:
|
|
90
|
+
msg = "logging provenance requires a parsed Python syntax tree"
|
|
91
|
+
raise ValueError(msg)
|
|
92
|
+
self.tree = tree
|
|
93
|
+
self.parents = context.parents
|
|
94
|
+
self.imports = context.imports
|
|
95
|
+
self.nodes = context.nodes(ast.AST)
|
|
96
|
+
self.import_state = self._module_import_state()
|
|
97
|
+
self.escaped_names = self._escaped_module_names()
|
|
98
|
+
self.mutated_operations: set[str] = set()
|
|
99
|
+
self._collect_mutations()
|
|
100
|
+
self.binding_counts: dict[tuple[ast.AST, str], int] = {}
|
|
101
|
+
self.binding_loads: dict[_LoggerBinding, list[ast.Name]] = {}
|
|
102
|
+
self.bindings = self._logger_bindings()
|
|
103
|
+
|
|
104
|
+
def uses(self) -> tuple[ApplicationLoggingUse, ...]:
|
|
105
|
+
findings = [use for call in self.context.nodes(ast.Call) if (use := self._call_use(call)) is not None]
|
|
106
|
+
return tuple(sorted(set(findings), key=lambda item: (item.line, item.column, item.operation)))
|
|
107
|
+
|
|
108
|
+
def _call_use(self, call: ast.Call) -> ApplicationLoggingUse | None:
|
|
109
|
+
qualified = self._qualified(call.func)
|
|
110
|
+
if qualified in _BUILTIN_SINKS:
|
|
111
|
+
return self._use(call, qualified, "sink")
|
|
112
|
+
if qualified == "logging.basicConfig" and self._uses_default_handlers(call):
|
|
113
|
+
return self._use(call, qualified, "sink")
|
|
114
|
+
if qualified in _MODULE_EMITTERS:
|
|
115
|
+
return self._use(call, qualified, "event")
|
|
116
|
+
if not isinstance(call.func, ast.Attribute) or call.func.attr not in _PUBLIC_EMITTERS:
|
|
117
|
+
return None
|
|
118
|
+
operation = "logging.Logger." + call.func.attr
|
|
119
|
+
if operation in self.mutated_operations or not self._logger_receiver(call.func.value):
|
|
120
|
+
return None
|
|
121
|
+
return self._use(call, operation, "event")
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _uses_default_handlers(call: ast.Call) -> bool:
|
|
125
|
+
handlers = next((keyword.value for keyword in call.keywords if keyword.arg == "handlers"), None)
|
|
126
|
+
default_handlers = handlers is None or (isinstance(handlers, ast.Constant) and handlers.value is None)
|
|
127
|
+
return default_handlers and not any(keyword.arg is None for keyword in call.keywords)
|
|
128
|
+
|
|
129
|
+
def _use(self, call: ast.Call, operation: str, kind: Literal["event", "sink"]) -> ApplicationLoggingUse:
|
|
130
|
+
line = self.context.source_lines[call.lineno - 1]
|
|
131
|
+
column = len(line.encode("utf-8")[: call.col_offset].decode("utf-8")) + 1
|
|
132
|
+
return ApplicationLoggingUse(call.lineno, column, operation, kind)
|
|
133
|
+
|
|
134
|
+
def _scope(self, node: ast.AST) -> ast.AST:
|
|
135
|
+
current = self.parents.get(node)
|
|
136
|
+
while current is not None and not isinstance(current, _SCOPES):
|
|
137
|
+
current = self.parents.get(current)
|
|
138
|
+
return self.tree if current is None else current
|
|
139
|
+
|
|
140
|
+
def _module_import_state(self) -> _ModuleImportState:
|
|
141
|
+
lines: dict[str, int] = {}
|
|
142
|
+
star_line = 0
|
|
143
|
+
for statement in self.tree.body:
|
|
144
|
+
match statement:
|
|
145
|
+
case ast.Import():
|
|
146
|
+
for alias in statement.names:
|
|
147
|
+
lines[alias.asname or alias.name.partition(".")[0]] = statement.lineno
|
|
148
|
+
case ast.ImportFrom():
|
|
149
|
+
for alias in statement.names:
|
|
150
|
+
if alias.name == "*":
|
|
151
|
+
star_line = max(star_line, statement.lineno)
|
|
152
|
+
else:
|
|
153
|
+
lines[alias.asname or alias.name] = statement.lineno
|
|
154
|
+
case _:
|
|
155
|
+
pass
|
|
156
|
+
return _ModuleImportState(lines, star_line, self._logging_module_aliases(), self._logging_root_aliases())
|
|
157
|
+
|
|
158
|
+
def _logging_module_aliases(self) -> frozenset[str]:
|
|
159
|
+
return frozenset(
|
|
160
|
+
alias.asname or alias.name.partition(".")[0]
|
|
161
|
+
for statement in self.tree.body
|
|
162
|
+
if isinstance(statement, ast.Import)
|
|
163
|
+
for alias in statement.names
|
|
164
|
+
if alias.name == "logging" or alias.name.startswith("logging.")
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def _logging_root_aliases(self) -> frozenset[str]:
|
|
168
|
+
return frozenset(
|
|
169
|
+
alias.asname or alias.name
|
|
170
|
+
for statement in self.tree.body
|
|
171
|
+
if isinstance(statement, ast.ImportFrom) and statement.level == 0 and statement.module == "logging"
|
|
172
|
+
for alias in statement.names
|
|
173
|
+
if alias.name == "root"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def _escaped_module_names(self) -> frozenset[str]:
|
|
177
|
+
module_names = self.import_state.module_names
|
|
178
|
+
root_names = self.import_state.root_names
|
|
179
|
+
imported_names = module_names | root_names
|
|
180
|
+
escaped: set[str] = set()
|
|
181
|
+
for node in self.context.nodes(ast.Name):
|
|
182
|
+
if node.id not in imported_names or not isinstance(node.ctx, ast.Load):
|
|
183
|
+
continue
|
|
184
|
+
parent = self.parents.get(node)
|
|
185
|
+
if not isinstance(parent, ast.Attribute):
|
|
186
|
+
escaped.add(node.id)
|
|
187
|
+
continue
|
|
188
|
+
if (node.id in module_names and parent.attr == "__dict__") or (
|
|
189
|
+
node.id in root_names and self._root_reference_escapes(parent)
|
|
190
|
+
):
|
|
191
|
+
escaped.add(node.id)
|
|
192
|
+
escaped_targets = {
|
|
193
|
+
(target.module, target.symbol)
|
|
194
|
+
for name in escaped
|
|
195
|
+
if (target := self.imports.bindings.get(name)) is not None
|
|
196
|
+
}
|
|
197
|
+
return frozenset(
|
|
198
|
+
name
|
|
199
|
+
for name in imported_names
|
|
200
|
+
if name in escaped
|
|
201
|
+
or (
|
|
202
|
+
(target := self.imports.bindings.get(name)) is not None
|
|
203
|
+
and (target.module, target.symbol) in escaped_targets
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def _root_reference_escapes(self, parent: ast.Attribute) -> bool:
|
|
208
|
+
if parent.attr not in _PUBLIC_EMITTERS | _ROUTING_METHODS:
|
|
209
|
+
return parent.attr not in _ROUTING_ATTRIBUTES
|
|
210
|
+
call = self.parents.get(parent)
|
|
211
|
+
return isinstance(parent.ctx, ast.Load) and (not isinstance(call, ast.Call) or call.func is not parent)
|
|
212
|
+
|
|
213
|
+
def _qualified(self, expression: ast.expr) -> str | None:
|
|
214
|
+
root = expression
|
|
215
|
+
while isinstance(root, ast.Attribute):
|
|
216
|
+
root = root.value
|
|
217
|
+
if not isinstance(root, ast.Name) or root.id in self.escaped_names:
|
|
218
|
+
return None
|
|
219
|
+
if self.import_state.import_lines.get(root.id, 0) <= self.import_state.star_import_line:
|
|
220
|
+
return None
|
|
221
|
+
name = self.imports.resolved_qualified_name(expression)
|
|
222
|
+
if name is None:
|
|
223
|
+
return None
|
|
224
|
+
parts = name.split(".")
|
|
225
|
+
if any(".".join(parts[:end]) in self.mutated_operations for end in range(1, len(parts) + 1)):
|
|
226
|
+
return None
|
|
227
|
+
return name
|
|
228
|
+
|
|
229
|
+
def _collect_mutations(self) -> None:
|
|
230
|
+
for node in self.context.nodes(ast.Attribute):
|
|
231
|
+
if node.attr == "__dict__" and (
|
|
232
|
+
self._factory_receiver(node.value) or self.imports.resolved_qualified_name(node.value) == "logging.root"
|
|
233
|
+
):
|
|
234
|
+
self.mutated_operations.update("logging.Logger." + name for name in _PUBLIC_EMITTERS)
|
|
235
|
+
if isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
236
|
+
self._record_attribute_mutation(node)
|
|
237
|
+
for call in self.context.nodes(ast.Call):
|
|
238
|
+
attribute = self._mutation_call_attribute(call)
|
|
239
|
+
if attribute is not None:
|
|
240
|
+
self._record_attribute_mutation(attribute)
|
|
241
|
+
|
|
242
|
+
def _record_attribute_mutation(self, attribute: ast.Attribute) -> None:
|
|
243
|
+
qualified = self._qualified(attribute)
|
|
244
|
+
if self._factory_receiver(attribute.value):
|
|
245
|
+
qualified = "logging.Logger." + attribute.attr
|
|
246
|
+
if qualified is not None:
|
|
247
|
+
self.mutated_operations.add(qualified)
|
|
248
|
+
|
|
249
|
+
def _mutation_call_attribute(self, call: ast.Call) -> ast.Attribute | None:
|
|
250
|
+
if not isinstance(call.func, ast.Name) or call.func.id not in {"setattr", "delattr"}:
|
|
251
|
+
return None
|
|
252
|
+
if not self.imports.builtin_is_unshadowed(call.func.id) or len(call.args) < _MIN_ATTRIBUTE_MUTATION_ARGS:
|
|
253
|
+
return None
|
|
254
|
+
name = call.args[1]
|
|
255
|
+
if not isinstance(name, ast.Constant) or not isinstance(name.value, str):
|
|
256
|
+
return None
|
|
257
|
+
return ast.Attribute(value=call.args[0], attr=name.value, ctx=ast.Load())
|
|
258
|
+
|
|
259
|
+
def _factory_receiver(self, expression: ast.expr) -> bool:
|
|
260
|
+
return (
|
|
261
|
+
isinstance(expression, ast.Call) and self._qualified(expression.func) in _LOGGER_FACTORIES
|
|
262
|
+
) or self._qualified(expression) == "logging.root"
|
|
263
|
+
|
|
264
|
+
def _logger_bindings(self) -> dict[tuple[ast.AST, str], _LoggerBinding]:
|
|
265
|
+
assignments = self._index_bindings()
|
|
266
|
+
candidates: dict[tuple[ast.AST, str], _LoggerBinding] = {}
|
|
267
|
+
for binding in assignments:
|
|
268
|
+
if self._is_logger_binding(binding):
|
|
269
|
+
candidates[binding.scope, binding.name] = binding
|
|
270
|
+
self.bindings = candidates
|
|
271
|
+
self.binding_loads = {}
|
|
272
|
+
for node in self.context.nodes(ast.Name):
|
|
273
|
+
if isinstance(node.ctx, ast.Load) and (binding := self._binding(node)) is not None:
|
|
274
|
+
self.binding_loads.setdefault(binding, []).append(node)
|
|
275
|
+
return {key: binding for key, binding in candidates.items() if self._nonescaping(binding)}
|
|
276
|
+
|
|
277
|
+
def _index_bindings(self) -> list[_LoggerBinding]:
|
|
278
|
+
counts: dict[tuple[ast.AST, str], int] = {}
|
|
279
|
+
assignments: list[_LoggerBinding] = []
|
|
280
|
+
for node in self.nodes:
|
|
281
|
+
name = self._bound_name(node)
|
|
282
|
+
if name is not None:
|
|
283
|
+
key = (self._scope(node), name)
|
|
284
|
+
counts[key] = counts.get(key, 0) + 1
|
|
285
|
+
match node:
|
|
286
|
+
case ast.Global() | ast.Nonlocal():
|
|
287
|
+
for name in node.names:
|
|
288
|
+
counts[self._scope(node), name] = 2
|
|
289
|
+
case ast.Import() | ast.ImportFrom():
|
|
290
|
+
for alias in node.names:
|
|
291
|
+
local = alias.asname or alias.name.partition(".")[0]
|
|
292
|
+
key = (self._scope(node), local)
|
|
293
|
+
counts[key] = counts.get(key, 0) + 1
|
|
294
|
+
case _:
|
|
295
|
+
pass
|
|
296
|
+
assignment = self._logger_assignment(node)
|
|
297
|
+
if assignment is not None:
|
|
298
|
+
assignments.append(assignment)
|
|
299
|
+
self.binding_counts = counts
|
|
300
|
+
return assignments
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def _bound_name(node: ast.AST) -> str | None:
|
|
304
|
+
match node:
|
|
305
|
+
case ast.Name(id=name, ctx=ast.Store() | ast.Del()):
|
|
306
|
+
return name
|
|
307
|
+
case ast.arg(arg=name):
|
|
308
|
+
return name
|
|
309
|
+
case (
|
|
310
|
+
ast.FunctionDef(name=name)
|
|
311
|
+
| ast.AsyncFunctionDef(name=name)
|
|
312
|
+
| ast.ClassDef(name=name)
|
|
313
|
+
| ast.ExceptHandler(name=name)
|
|
314
|
+
| ast.MatchAs(name=name)
|
|
315
|
+
| ast.MatchStar(name=name)
|
|
316
|
+
):
|
|
317
|
+
return name
|
|
318
|
+
case ast.MatchMapping(rest=name):
|
|
319
|
+
return name
|
|
320
|
+
case _:
|
|
321
|
+
pass
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
def _logger_assignment(self, node: ast.AST) -> _LoggerBinding | None:
|
|
325
|
+
match node:
|
|
326
|
+
case ast.Assign(targets=[ast.Name(id=name)]):
|
|
327
|
+
return _LoggerBinding(self._scope(node), name, node)
|
|
328
|
+
case ast.AnnAssign(target=ast.Name(id=name), value=value) if value is not None:
|
|
329
|
+
return _LoggerBinding(self._scope(node), name, node)
|
|
330
|
+
case _:
|
|
331
|
+
pass
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
def _is_logger_binding(self, binding: _LoggerBinding) -> bool:
|
|
335
|
+
assignment = binding.assignment
|
|
336
|
+
if binding.scope is self.tree and assignment.lineno <= self.import_state.star_import_line:
|
|
337
|
+
return False
|
|
338
|
+
if self.binding_counts.get((binding.scope, binding.name)) != 1:
|
|
339
|
+
return False
|
|
340
|
+
if self.parents.get(assignment) is not binding.scope:
|
|
341
|
+
return False
|
|
342
|
+
return isinstance(assignment.value, ast.Call) and self._qualified(assignment.value.func) in _LOGGER_FACTORIES
|
|
343
|
+
|
|
344
|
+
def _binding(self, node: ast.Name) -> _LoggerBinding | None:
|
|
345
|
+
scope = self._scope(node)
|
|
346
|
+
origin = scope
|
|
347
|
+
while scope is not None:
|
|
348
|
+
if not (isinstance(scope, ast.ClassDef) and scope is not origin):
|
|
349
|
+
key = (scope, node.id)
|
|
350
|
+
if key in self.binding_counts:
|
|
351
|
+
return self.bindings.get(key)
|
|
352
|
+
parent = self._scope(scope)
|
|
353
|
+
scope = None if parent is scope else parent
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
def _nonescaping(self, binding: _LoggerBinding) -> bool:
|
|
357
|
+
for node in self.binding_loads.get(binding, ()):
|
|
358
|
+
parent = self.parents.get(node)
|
|
359
|
+
if not isinstance(parent, ast.Attribute):
|
|
360
|
+
return False
|
|
361
|
+
if parent.attr in _PUBLIC_EMITTERS:
|
|
362
|
+
call = self.parents.get(parent)
|
|
363
|
+
if not isinstance(parent.ctx, ast.Load) or not isinstance(call, ast.Call) or call.func is not parent:
|
|
364
|
+
return False
|
|
365
|
+
elif parent.attr in _ROUTING_METHODS:
|
|
366
|
+
call = self.parents.get(parent)
|
|
367
|
+
if not isinstance(call, ast.Call) or call.func is not parent:
|
|
368
|
+
return False
|
|
369
|
+
elif parent.attr not in _ROUTING_ATTRIBUTES:
|
|
370
|
+
return False
|
|
371
|
+
return True
|
|
372
|
+
|
|
373
|
+
def _logger_receiver(self, expression: ast.expr) -> bool:
|
|
374
|
+
if self._factory_receiver(expression):
|
|
375
|
+
return True
|
|
376
|
+
if isinstance(expression, ast.Name):
|
|
377
|
+
binding = self._binding(expression)
|
|
378
|
+
return binding is not None and binding.assignment.lineno < expression.lineno
|
|
379
|
+
return False
|
{sarj_python_lint-1.77.0 → sarj_python_lint-1.77.3}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass, replace
|
|
|
5
5
|
from functools import cached_property
|
|
6
6
|
from types import MappingProxyType
|
|
7
7
|
from typing import TYPE_CHECKING, final
|
|
8
|
+
from weakref import ReferenceType, ref
|
|
8
9
|
|
|
9
10
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
10
11
|
from sarj_python_lint._file_context import PythonFileContext
|
|
@@ -359,8 +360,10 @@ def _contains_model(shape: _Shape) -> bool:
|
|
|
359
360
|
|
|
360
361
|
@final
|
|
361
362
|
class CopyOnWriteAnalysis:
|
|
362
|
-
def __init__(self, context: PythonFileContext) -> None:
|
|
363
|
-
self.
|
|
363
|
+
def __init__(self, context: PythonFileContext, *, borrow_context: bool = False) -> None:
|
|
364
|
+
self._context: PythonFileContext | ReferenceType[PythonFileContext] = (
|
|
365
|
+
ref(context) if borrow_context else context
|
|
366
|
+
)
|
|
364
367
|
self.models = _Models(context)
|
|
365
368
|
self.mutations: list[Mutation] = []
|
|
366
369
|
self.discarded: list[ast.Call] = []
|
|
@@ -374,6 +377,14 @@ class CopyOnWriteAnalysis:
|
|
|
374
377
|
for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
|
|
375
378
|
self._function(function)
|
|
376
379
|
|
|
380
|
+
@property
|
|
381
|
+
def context(self) -> PythonFileContext:
|
|
382
|
+
context = self._context() if isinstance(self._context, ReferenceType) else self._context
|
|
383
|
+
if context is None:
|
|
384
|
+
msg = "The copy-on-write file context has been released"
|
|
385
|
+
raise ReferenceError(msg)
|
|
386
|
+
return context
|
|
387
|
+
|
|
377
388
|
@cached_property
|
|
378
389
|
def _validator_provider(self) -> ResourceProvenance:
|
|
379
390
|
return self.context.static_resource_provenance
|