sarj-python-lint 1.39.0__tar.gz → 1.40.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_registry.py +2 -0
- sarj_python_lint-1.40.0/src/sarj_python_lint/rules/no_loguru_printf_format.py +170 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +1 -1
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +2 -2
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +1 -1
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +50 -56
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/.gitignore +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/LICENSE +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/README.md +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/unused_test_factory_option.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.40.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -67,6 +67,7 @@ from sarj_python_lint.rules.no_input_model_mutation import NoInputModelMutation
|
|
|
67
67
|
from sarj_python_lint.rules.no_interpreter_source_arguments import NoInterpreterSourceArguments
|
|
68
68
|
from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvalidArgumentNameSuppression
|
|
69
69
|
from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
|
|
70
|
+
from sarj_python_lint.rules.no_loguru_printf_format import NoLoguruPrintfFormat
|
|
70
71
|
from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
|
|
71
72
|
from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
|
|
72
73
|
from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
|
|
@@ -245,6 +246,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
245
246
|
AsyncMockCallWithoutAwaitAssertion.id: AsyncMockCallWithoutAwaitAssertion,
|
|
246
247
|
NoAsyncFunctionCache.id: NoAsyncFunctionCache,
|
|
247
248
|
NoLoguruExcInfo.id: NoLoguruExcInfo,
|
|
249
|
+
NoLoguruPrintfFormat.id: NoLoguruPrintfFormat,
|
|
248
250
|
ComplexPostgresQueryRequiresArchitectureReview.id: ComplexPostgresQueryRequiresArchitectureReview,
|
|
249
251
|
ExcessiveCommentary.id: ExcessiveCommentary,
|
|
250
252
|
PreferWalrusAwaitedNoneGuard.id: PreferWalrusAwaitedNoneGuard,
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
import re
|
|
6
|
+
from string import Formatter
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
8
|
+
|
|
9
|
+
from sarj_python_lint.rule_base import (
|
|
10
|
+
AutofixPolicy,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
ExampleFile,
|
|
13
|
+
ExampleOutcome,
|
|
14
|
+
Rule,
|
|
15
|
+
RuleCategory,
|
|
16
|
+
RuleDocumentation,
|
|
17
|
+
RuleExample,
|
|
18
|
+
Severity,
|
|
19
|
+
is_suppressed,
|
|
20
|
+
)
|
|
21
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_METHODS = frozenset({"trace", "debug", "info", "success", "warning", "error", "critical", "exception", "log"})
|
|
29
|
+
_LOGURU = frozenset({"loguru"})
|
|
30
|
+
_CONVERSION = re.compile(r"%(?:\([^()]*\))?[-+ #0]*+(?:[0-9]+|\*)?(?:\.(?:[0-9]*|\*))?[hlL]?[diouxXeEfFgGcrsa]")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@final
|
|
34
|
+
class NoLoguruPrintfFormat(Rule):
|
|
35
|
+
id = "no-loguru-printf-format"
|
|
36
|
+
code = "SARJ509"
|
|
37
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
38
|
+
default_level=Severity.WARNING,
|
|
39
|
+
summary="Use brace formatting for positional values passed to Loguru.",
|
|
40
|
+
rationale=(
|
|
41
|
+
"Loguru formats messages with str.format rather than printf interpolation. A percent-style message "
|
|
42
|
+
"with positional values keeps its placeholders and loses the values from the rendered diagnostic."
|
|
43
|
+
),
|
|
44
|
+
remediation=(
|
|
45
|
+
"Use Loguru brace fields with equivalent formatting, or retain diagnostic values as named structured "
|
|
46
|
+
"fields. Review numeric, mapping and width specifications rather than mechanically replacing percent signs."
|
|
47
|
+
),
|
|
48
|
+
category=RuleCategory.CORRECTNESS,
|
|
49
|
+
autofix=AutofixPolicy.NONE,
|
|
50
|
+
limitations=(
|
|
51
|
+
"Only unambiguous module-level Loguru imports and immediate bind/opt chains with a literal message and supplied positional formatting arguments are checked.",
|
|
52
|
+
"The message must contain a valid percent conversion and no brace fields. Escaped percent signs alone, malformed templates, dynamic messages and expanded arguments are excluded.",
|
|
53
|
+
"Standard logging, unknown receivers, stored aliases, shadowed or escaped imports, custom wrapper chains, keyword-only fields and generated files are excluded.",
|
|
54
|
+
"Visible namespace or logger mutations invalidate import proof. Interprocedural effects and runtime monkeypatches are not resolved.",
|
|
55
|
+
"Argument types and printf arity are not inferred. The warning requests review; no automatic conversion changes formatting or structured logging contracts.",
|
|
56
|
+
),
|
|
57
|
+
examples=(
|
|
58
|
+
RuleExample(
|
|
59
|
+
example_id="printf-values-ignored",
|
|
60
|
+
title="Loguru leaves printf placeholders unchanged",
|
|
61
|
+
outcome=ExampleOutcome.MATCH,
|
|
62
|
+
files=(
|
|
63
|
+
ExampleFile.python(
|
|
64
|
+
"app/service.py",
|
|
65
|
+
'from loguru import logger\nlogger.error("Request %s failed with status %d", "request-marker", 503)\n',
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
69
|
+
expected_count=1,
|
|
70
|
+
public=True,
|
|
71
|
+
),
|
|
72
|
+
RuleExample(
|
|
73
|
+
example_id="brace-values-rendered",
|
|
74
|
+
title="Brace fields render positional diagnostic values",
|
|
75
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
76
|
+
files=(
|
|
77
|
+
ExampleFile.python(
|
|
78
|
+
"app/service.py",
|
|
79
|
+
'from loguru import logger\nlogger.error("Request {} failed with status {}", "request-marker", 503)\n',
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
83
|
+
expected_count=0,
|
|
84
|
+
public=True,
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
description = documentation.summary
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
92
|
+
if "%" not in context.source or "loguru" not in context.source or context.generated or context.tree is None:
|
|
93
|
+
return []
|
|
94
|
+
provenance: ResourceProvenance | None = None
|
|
95
|
+
diagnostics: list[Diagnostic] = []
|
|
96
|
+
for call in context.nodes(ast.Call):
|
|
97
|
+
if not isinstance(call.func, ast.Attribute) or call.func.attr not in _METHODS or not _printf_message(call):
|
|
98
|
+
continue
|
|
99
|
+
receiver = _logger_root(call.func.value)
|
|
100
|
+
if receiver is None or not context.imports.resolves(receiver, sources=_LOGURU, symbol="logger"):
|
|
101
|
+
continue
|
|
102
|
+
if provenance is None:
|
|
103
|
+
provenance = ResourceProvenance(context, require_static_namespace=True)
|
|
104
|
+
if not _logger_imports_untouched(context, provenance):
|
|
105
|
+
return []
|
|
106
|
+
if not provenance.imported(receiver, "loguru.logger") or is_suppressed(
|
|
107
|
+
context.source_lines, call.lineno, self.code
|
|
108
|
+
):
|
|
109
|
+
continue
|
|
110
|
+
diagnostics.append(
|
|
111
|
+
Diagnostic(
|
|
112
|
+
path=context.path,
|
|
113
|
+
line=call.lineno,
|
|
114
|
+
col=call.col_offset + 1,
|
|
115
|
+
code=self.code,
|
|
116
|
+
message="Loguru leaves printf placeholders unchanged; use brace fields or named structured fields for these values.",
|
|
117
|
+
severity=Severity.WARNING,
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
return diagnostics
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _logger_imports_untouched(context: PythonFileContext, provenance: ResourceProvenance) -> bool:
|
|
124
|
+
for node in context.nodes(ast.Name, ast.Attribute):
|
|
125
|
+
qualified = context.imports.resolved_qualified_name(node)
|
|
126
|
+
if qualified in {"loguru", "loguru.logger"} and not provenance.imported(node, qualified):
|
|
127
|
+
return False
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _logger_root(receiver: ast.expr) -> ast.expr | None:
|
|
132
|
+
while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
|
|
133
|
+
if (
|
|
134
|
+
receiver.func.attr not in {"bind", "opt"}
|
|
135
|
+
or any(isinstance(argument, ast.Starred) for argument in receiver.args)
|
|
136
|
+
or any(keyword.arg is None for keyword in receiver.keywords)
|
|
137
|
+
):
|
|
138
|
+
return None
|
|
139
|
+
receiver = receiver.func.value
|
|
140
|
+
return receiver
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _printf_message(call: ast.Call) -> bool:
|
|
144
|
+
index = int(isinstance(call.func, ast.Attribute) and call.func.attr == "log")
|
|
145
|
+
if (
|
|
146
|
+
len(call.args) <= index + 1
|
|
147
|
+
or any(isinstance(argument, ast.Starred) for argument in call.args)
|
|
148
|
+
or any(keyword.arg is None for keyword in call.keywords)
|
|
149
|
+
):
|
|
150
|
+
return False
|
|
151
|
+
message = call.args[index]
|
|
152
|
+
if not isinstance(message, ast.Constant) or not isinstance(message.value, str):
|
|
153
|
+
return False
|
|
154
|
+
try:
|
|
155
|
+
if any(field is not None for _, field, _, _ in Formatter().parse(message.value)):
|
|
156
|
+
return False
|
|
157
|
+
except ValueError:
|
|
158
|
+
return False
|
|
159
|
+
position = 0
|
|
160
|
+
converted = False
|
|
161
|
+
while (position := message.value.find("%", position)) != -1:
|
|
162
|
+
if message.value.startswith("%%", position):
|
|
163
|
+
position += 2
|
|
164
|
+
continue
|
|
165
|
+
token = _CONVERSION.match(message.value, position)
|
|
166
|
+
if token is None:
|
|
167
|
+
return False
|
|
168
|
+
converted = True
|
|
169
|
+
position = token.end()
|
|
170
|
+
return converted
|
|
@@ -307,7 +307,7 @@ def _is_skipped_path(path: Path) -> bool:
|
|
|
307
307
|
"snapshots",
|
|
308
308
|
}
|
|
309
309
|
)
|
|
310
|
-
return is_test_path(path) or
|
|
310
|
+
return is_test_path(path) or not excluded_parts.isdisjoint(part.lower() for part in path.parts)
|
|
311
311
|
|
|
312
312
|
|
|
313
313
|
def _exclude_scaffolding_strings(node: ast.Call, excluded: set[int]) -> None:
|
|
@@ -295,7 +295,7 @@ def _name_role(identifier: str) -> _Role:
|
|
|
295
295
|
if not _is_authenticator_name(identifier):
|
|
296
296
|
return _Role(external=False, expected=False)
|
|
297
297
|
first = leading_word(identifier) or ""
|
|
298
|
-
external = first in _EXTERNAL_PREFIXES or ("header" in tokens and
|
|
298
|
+
external = first in _EXTERNAL_PREFIXES or ("header" in tokens and not {"auth", "authorization"}.isdisjoint(tokens))
|
|
299
299
|
expected = first in _EXPECTED_PREFIXES or identifier.isupper()
|
|
300
300
|
return _Role(external=external, expected=expected, password=_node_name_is_password(identifier))
|
|
301
301
|
|
|
@@ -400,7 +400,7 @@ def _node_is_password(node: ast.expr) -> bool:
|
|
|
400
400
|
|
|
401
401
|
|
|
402
402
|
def _node_name_is_password(identifier: str) -> bool:
|
|
403
|
-
return
|
|
403
|
+
return not {"passwd", "password", "passwords"}.isdisjoint(identifier_tokens(identifier))
|
|
404
404
|
|
|
405
405
|
|
|
406
406
|
def _environment_names(module: ast.Module) -> frozenset[str]:
|
|
@@ -204,7 +204,7 @@ def _ignores_argument_type(comment: str) -> bool:
|
|
|
204
204
|
if match is None:
|
|
205
205
|
return False
|
|
206
206
|
codes = match.group(1)
|
|
207
|
-
return codes is None or
|
|
207
|
+
return codes is None or not {"reportArgumentType", "arg-type"}.isdisjoint(code.strip() for code in codes.split(","))
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
def _module_scope_classdefs(tree: ast.Module) -> list[ast.ClassDef]:
|
|
@@ -59,9 +59,10 @@ class PreferSetIsdisjoint(ProjectRule):
|
|
|
59
59
|
category=RuleCategory.STYLE,
|
|
60
60
|
autofix=AutofixPolicy.SUGGESTION,
|
|
61
61
|
limitations=(
|
|
62
|
+
"Explicit unshadowed built-in bool calls are predicates even in assignments and returns. Raw stored or returned intersections remain values, not predicates.",
|
|
62
63
|
"Set receivers must come from a literal, comprehension, constructor, dominating local assignment, or an unchanged parameter's directly declared first-party set/frozenset field.",
|
|
63
64
|
"Dictionary key-view receivers require a literal/comprehension/constructor or one unreassigned, dominating local binding. Custom mappings and unproven parameters are excluded.",
|
|
64
|
-
"Declared fields rely on the annotated contract. Unknown or union field types, properties, explicit owner/field reassignment, branch-merged bindings, stored intersections, and generated files are excluded. The other operand must be a known iterable.",
|
|
65
|
+
"Declared fields rely on the annotated contract. Unknown or union field types, properties, explicit owner/field reassignment, branch-merged bindings, stored intersections, and generated files are excluded. Captured bindings in deferred lambda/generator bodies and starred intersection arguments are excluded. The other operand must be a known iterable.",
|
|
65
66
|
"The suggestion is intentionally not an autofix because short-circuiting may make custom element equality or hashing side effects observable.",
|
|
66
67
|
),
|
|
67
68
|
examples=(
|
|
@@ -73,7 +74,7 @@ class PreferSetIsdisjoint(ProjectRule):
|
|
|
73
74
|
ExampleFile(PurePosixPath("pyproject.toml"), '[project]\nname = "example"\nversion = "0.1.0"\n'),
|
|
74
75
|
ExampleFile.python(
|
|
75
76
|
"app/policy.py",
|
|
76
|
-
"class AccessCase:\n tags: frozenset[str]\n\ndef accepts(case: AccessCase):\n
|
|
77
|
+
"class AccessCase:\n tags: frozenset[str]\n\ndef accepts(case: AccessCase):\n return bool(case.tags & {'read', 'write'})\n",
|
|
77
78
|
),
|
|
78
79
|
),
|
|
79
80
|
focus_path=PurePosixPath("app/policy.py"),
|
|
@@ -144,18 +145,22 @@ class _Scanner:
|
|
|
144
145
|
self._update_binding(statement, local)
|
|
145
146
|
|
|
146
147
|
def _scan_statement(self, statement: ast.stmt, exact: set[str]) -> None:
|
|
148
|
+
exact.difference_update(_named_expression_targets(statement))
|
|
149
|
+
if isinstance(
|
|
150
|
+
statement,
|
|
151
|
+
ast.If | ast.While | ast.For | ast.AsyncFor | ast.With | ast.AsyncWith | ast.Try | ast.Match,
|
|
152
|
+
):
|
|
153
|
+
stored = _stored_names(statement)
|
|
154
|
+
if "*" in stored:
|
|
155
|
+
exact.clear()
|
|
156
|
+
else:
|
|
157
|
+
exact.difference_update(stored)
|
|
147
158
|
match statement:
|
|
148
|
-
case ast.If():
|
|
159
|
+
case ast.If() | ast.While():
|
|
149
160
|
self._scan_boolean(statement.test, exact)
|
|
150
161
|
self._scan_embedded(statement.test, exact)
|
|
151
162
|
self.scan_body(statement.body, set(exact))
|
|
152
163
|
self.scan_body(statement.orelse, set(exact))
|
|
153
|
-
case ast.While():
|
|
154
|
-
loop_exact = exact - _stored_names(ast.Module(body=[*statement.body, *statement.orelse]))
|
|
155
|
-
self._scan_boolean(statement.test, loop_exact)
|
|
156
|
-
self._scan_embedded(statement.test, loop_exact)
|
|
157
|
-
self.scan_body(statement.body, set(exact))
|
|
158
|
-
self.scan_body(statement.orelse, set(exact))
|
|
159
164
|
case ast.Assert(test=test):
|
|
160
165
|
self._scan_boolean(test, exact)
|
|
161
166
|
self._scan_embedded(test, exact)
|
|
@@ -172,7 +177,7 @@ class _Scanner:
|
|
|
172
177
|
case ast.With() | ast.AsyncWith():
|
|
173
178
|
nested = set(exact)
|
|
174
179
|
for item in statement.items:
|
|
175
|
-
self._scan_embedded(item.context_expr,
|
|
180
|
+
self._scan_embedded(item.context_expr, nested)
|
|
176
181
|
if item.optional_vars is not None:
|
|
177
182
|
nested.difference_update(_stored_names(item.optional_vars))
|
|
178
183
|
self.scan_body(statement.body, nested)
|
|
@@ -192,9 +197,14 @@ class _Scanner:
|
|
|
192
197
|
|
|
193
198
|
def _scan_embedded(self, expression: ast.expr, exact: set[str]) -> None:
|
|
194
199
|
if isinstance(expression, ast.Lambda):
|
|
195
|
-
|
|
196
|
-
self._scan_embedded(expression.body, nested)
|
|
200
|
+
self._scan_embedded(expression.body, set())
|
|
197
201
|
return
|
|
202
|
+
if _is_builtin_bool_call(expression, self.shadowed) or (
|
|
203
|
+
isinstance(expression, ast.UnaryOp)
|
|
204
|
+
and isinstance(expression.op, ast.Not)
|
|
205
|
+
and _is_builtin_bool_call(expression.operand, self.shadowed)
|
|
206
|
+
):
|
|
207
|
+
self._scan_boolean(expression, exact)
|
|
198
208
|
if isinstance(expression, ast.IfExp):
|
|
199
209
|
self._scan_boolean(expression.test, exact)
|
|
200
210
|
if isinstance(expression, ast.DictComp):
|
|
@@ -202,6 +212,9 @@ class _Scanner:
|
|
|
202
212
|
self._scan_embedded(expression.key, nested)
|
|
203
213
|
self._scan_embedded(expression.value, nested)
|
|
204
214
|
return
|
|
215
|
+
if isinstance(expression, ast.GeneratorExp):
|
|
216
|
+
self._scan_embedded(expression.generators[0].iter, exact)
|
|
217
|
+
exact = set()
|
|
205
218
|
if isinstance(expression, ast.ListComp | ast.SetComp | ast.GeneratorExp):
|
|
206
219
|
nested = self._scan_comprehensions(expression.generators, exact)
|
|
207
220
|
self._scan_embedded(expression.elt, nested)
|
|
@@ -252,7 +265,6 @@ class _Scanner:
|
|
|
252
265
|
)
|
|
253
266
|
|
|
254
267
|
def _update_binding(self, statement: ast.stmt, exact: set[str]) -> None:
|
|
255
|
-
exact.difference_update(_named_expression_targets(statement))
|
|
256
268
|
match statement:
|
|
257
269
|
case ast.Assign(targets=[ast.Name(id=name)], value=value):
|
|
258
270
|
if _is_exact_set(value, exact, self.shadowed):
|
|
@@ -274,17 +286,6 @@ class _Scanner:
|
|
|
274
286
|
exact.difference_update(alias.asname or alias.name.split(".")[0] for alias in aliases)
|
|
275
287
|
case ast.ImportFrom(names=aliases):
|
|
276
288
|
_invalidate_imported_bindings(aliases, exact)
|
|
277
|
-
case (
|
|
278
|
-
ast.If()
|
|
279
|
-
| ast.While()
|
|
280
|
-
| ast.For()
|
|
281
|
-
| ast.AsyncFor()
|
|
282
|
-
| ast.With()
|
|
283
|
-
| ast.AsyncWith()
|
|
284
|
-
| ast.Try()
|
|
285
|
-
| ast.Match()
|
|
286
|
-
):
|
|
287
|
-
exact.difference_update(_stored_names(statement))
|
|
288
289
|
case _:
|
|
289
290
|
pass
|
|
290
291
|
|
|
@@ -299,6 +300,7 @@ def _is_intersection(node: ast.expr, exact: set[str], shadowed: frozenset[str],
|
|
|
299
300
|
and isinstance(node.func, ast.Attribute)
|
|
300
301
|
and node.func.attr == "intersection"
|
|
301
302
|
and len(node.args) == 1
|
|
303
|
+
and not isinstance(node.args[0], ast.Starred)
|
|
302
304
|
and not node.keywords
|
|
303
305
|
and _is_exact_set(node.func.value, exact, shadowed)
|
|
304
306
|
)
|
|
@@ -356,45 +358,37 @@ def _is_proven_iterable(node: ast.expr, exact: set[str], shadowed: frozenset[str
|
|
|
356
358
|
)
|
|
357
359
|
|
|
358
360
|
|
|
359
|
-
def _stored_names(node: ast.AST) -> set[str]:
|
|
360
|
-
|
|
361
|
+
def _stored_names(node: ast.AST, *, node_index: NodeIndex | None = None) -> set[str]:
|
|
362
|
+
names: set[str] = set()
|
|
363
|
+
for child in walk_ast(node, index=node_index):
|
|
364
|
+
match child:
|
|
365
|
+
case (
|
|
366
|
+
ast.Name(id=name, ctx=ast.Store() | ast.Del())
|
|
367
|
+
| ast.arg(arg=name)
|
|
368
|
+
| ast.FunctionDef(name=name)
|
|
369
|
+
| ast.AsyncFunctionDef(name=name)
|
|
370
|
+
| ast.ClassDef(name=name)
|
|
371
|
+
| ast.ExceptHandler(name=name)
|
|
372
|
+
| ast.MatchAs(name=name)
|
|
373
|
+
| ast.MatchStar(name=name)
|
|
374
|
+
| ast.MatchMapping(rest=name)
|
|
375
|
+
):
|
|
376
|
+
if name is not None:
|
|
377
|
+
names.add(name)
|
|
378
|
+
case ast.alias(name=name, asname=alias):
|
|
379
|
+
names.add(alias or name.split(".")[0])
|
|
380
|
+
case _:
|
|
381
|
+
pass
|
|
382
|
+
return names
|
|
361
383
|
|
|
362
384
|
|
|
363
385
|
def _named_expression_targets(node: ast.AST) -> set[str]:
|
|
364
386
|
return {expression.target.id for expression in walk_ast(node) if isinstance(expression, ast.NamedExpr)}
|
|
365
387
|
|
|
366
388
|
|
|
367
|
-
def _argument_names(arguments: ast.arguments) -> set[str]:
|
|
368
|
-
return {
|
|
369
|
-
argument.arg
|
|
370
|
-
for argument in (
|
|
371
|
-
*arguments.posonlyargs,
|
|
372
|
-
*arguments.args,
|
|
373
|
-
*arguments.kwonlyargs,
|
|
374
|
-
*((arguments.vararg,) if arguments.vararg is not None else ()),
|
|
375
|
-
*((arguments.kwarg,) if arguments.kwarg is not None else ()),
|
|
376
|
-
)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
389
|
def _shadowed_builtins(tree: ast.Module, *, node_index: NodeIndex | None = None) -> frozenset[str]:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store) and node.id in _TRACKED_BUILTINS:
|
|
384
|
-
shadowed.add(node.id)
|
|
385
|
-
elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) and node.name in _TRACKED_BUILTINS:
|
|
386
|
-
shadowed.add(node.name)
|
|
387
|
-
elif isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names):
|
|
388
|
-
shadowed.update(_TRACKED_BUILTINS)
|
|
389
|
-
elif isinstance(node, ast.ExceptHandler | ast.MatchAs | ast.MatchStar) and node.name in _TRACKED_BUILTINS:
|
|
390
|
-
shadowed.add(node.name)
|
|
391
|
-
elif isinstance(node, ast.MatchMapping) and node.rest in _TRACKED_BUILTINS:
|
|
392
|
-
shadowed.add(node.rest)
|
|
393
|
-
elif isinstance(node, ast.arg) and node.arg in _TRACKED_BUILTINS:
|
|
394
|
-
shadowed.add(node.arg)
|
|
395
|
-
elif isinstance(node, ast.alias) and (node.asname or node.name.split(".")[0]) in _TRACKED_BUILTINS:
|
|
396
|
-
shadowed.add(node.asname or node.name.split(".")[0])
|
|
397
|
-
return frozenset(shadowed)
|
|
390
|
+
stored = _stored_names(tree, node_index=node_index)
|
|
391
|
+
return _TRACKED_BUILTINS if "*" in stored else frozenset(stored.intersection(_TRACKED_BUILTINS))
|
|
398
392
|
|
|
399
393
|
|
|
400
394
|
def _invalidate_imported_bindings(aliases: list[ast.alias], exact: set[str]) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.39.0 → sarj_python_lint-1.40.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|