sarj-python-lint 1.16.2__tar.gz → 1.16.4__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.16.2 → sarj_python_lint-1.16.4}/PKG-INFO +1 -1
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/pyproject.toml +1 -1
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +110 -21
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_public_dependency_contract.py +19 -2
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/unused_test_factory_option.py +124 -11
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/.gitignore +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/LICENSE +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/README.md +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.16.
|
|
3
|
+
Version: 1.16.4
|
|
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/
|
|
@@ -101,7 +101,8 @@ class PreferMatchTypeDispatch(Rule):
|
|
|
101
101
|
limitations=(
|
|
102
102
|
"General dispatch requires three or more adjacent, unguarded `isinstance` branches over the same simple name. Two arms are checked only for an exact `ast.Name.id` / `ast.Attribute.attr` projection followed by a None-returning fallback.",
|
|
103
103
|
"A terminating sibling prefix may precede a two-arm if/elif tail whose bodies can fall through. Preserve nested checks inside case bodies rather than moving them into case guards.",
|
|
104
|
-
"The checked types must be unshadowed builtins, undecorated module-local classes without an explicit metaclass, or proven stdlib ast classes; unresolved imports, runtime type groups, repeated type references, generated files, and non-terminating sibling checks are excluded.",
|
|
104
|
+
"The checked types must be unshadowed builtins, undecorated module-local classes without an explicit or known locally inherited custom metaclass, or proven stdlib ast classes; unresolved imports, runtime type groups, repeated type references, generated files, and non-terminating sibling checks are excluded.",
|
|
105
|
+
"Local ancestry must resolve to stable undeclared-metaclass classes or proven builtin/stdlib ast bases. External bases, runtime base aliases, and parameterized base expressions remain excluded because their metaclass or __class_getitem__ effects are unproven.",
|
|
105
106
|
"Nested attribute-validation guards are excluded: converting them to keyword class patterns can turn attribute errors into match fallthrough, and an imported isinstance operand may be a runtime tuple rather than a class.",
|
|
106
107
|
"A terminal-looking context-manager body does not prove a sibling branch terminates: exceptions can be suppressed. An unconditional return or raise after the context manager remains eligible.",
|
|
107
108
|
"Declared support for Python before 3.10 suppresses this recommendation when proven by the nearest project metadata or exact installed-distribution ownership. Missing or ambiguous target metadata retains advisory behavior; it does not prove a modern target.",
|
|
@@ -239,20 +240,20 @@ class PreferMatchTypeDispatch(Rule):
|
|
|
239
240
|
return []
|
|
240
241
|
imports = context.imports
|
|
241
242
|
all_nodes = tuple(context.nodes(ast.AST))
|
|
242
|
-
unsafe_bindings = _unsafe_local_bound_names(tree, all_nodes) | frozenset(
|
|
243
|
+
unsafe_bindings = _unsafe_local_bound_names(tree, all_nodes, imports) | frozenset(
|
|
243
244
|
qualified
|
|
244
245
|
for node in all_nodes
|
|
245
246
|
if isinstance(node, ast.Attribute)
|
|
246
247
|
and isinstance(node.ctx, (ast.Store, ast.Del))
|
|
247
248
|
and (qualified := imports.resolved_qualified_name(node)) is not None
|
|
248
|
-
and qualified.startswith("ast.")
|
|
249
|
+
and qualified.startswith(("ast.", "builtins."))
|
|
249
250
|
)
|
|
250
251
|
has_wildcard_import = any(
|
|
251
252
|
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names) for node in all_nodes
|
|
252
253
|
)
|
|
253
254
|
if not imports.builtin_is_unshadowed("isinstance") or "isinstance" in unsafe_bindings or has_wildcard_import:
|
|
254
255
|
return []
|
|
255
|
-
local_classes = _unshadowed_module_classes(tree, all_nodes)
|
|
256
|
+
local_classes = _unshadowed_module_classes(tree, all_nodes, imports, unsafe_bindings)
|
|
256
257
|
findings = _ladder_findings(
|
|
257
258
|
all_nodes,
|
|
258
259
|
path,
|
|
@@ -287,14 +288,10 @@ class PreferMatchTypeDispatch(Rule):
|
|
|
287
288
|
return findings
|
|
288
289
|
|
|
289
290
|
|
|
290
|
-
def _unshadowed_module_classes(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if isinstance(statement, ast.ClassDef)
|
|
295
|
-
and not statement.decorator_list
|
|
296
|
-
and not any(keyword.arg in {"metaclass", None} for keyword in statement.keywords)
|
|
297
|
-
]
|
|
291
|
+
def _unshadowed_module_classes(
|
|
292
|
+
tree: ast.Module, all_nodes: tuple[ast.AST, ...], imports: ImportIndex, unsafe_bindings: frozenset[str]
|
|
293
|
+
) -> frozenset[str]:
|
|
294
|
+
declarations = [statement for statement in tree.body if isinstance(statement, ast.ClassDef)]
|
|
298
295
|
rebound: set[str] = set()
|
|
299
296
|
for node in all_nodes:
|
|
300
297
|
match node:
|
|
@@ -310,17 +307,83 @@ def _unshadowed_module_classes(tree: ast.Module, all_nodes: tuple[ast.AST, ...])
|
|
|
310
307
|
case _:
|
|
311
308
|
pass
|
|
312
309
|
_add_module_import_bindings(tree, rebound)
|
|
313
|
-
|
|
310
|
+
names = [declaration.name for declaration in declarations]
|
|
311
|
+
stable = [
|
|
312
|
+
declaration
|
|
313
|
+
for declaration in declarations
|
|
314
|
+
if names.count(declaration.name) == 1 and declaration.name not in rebound
|
|
315
|
+
]
|
|
316
|
+
nominal = _nominal_class_ancestry(stable, imports, unsafe_bindings)
|
|
317
|
+
return frozenset(
|
|
318
|
+
declaration.name
|
|
319
|
+
for declaration in stable
|
|
320
|
+
if declaration.name in nominal
|
|
321
|
+
and not any(keyword.arg in {"metaclass", None} for keyword in declaration.keywords)
|
|
322
|
+
)
|
|
314
323
|
|
|
315
324
|
|
|
316
|
-
def
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
325
|
+
def _nominal_class_ancestry(
|
|
326
|
+
declarations: list[ast.ClassDef], imports: ImportIndex, unsafe_bindings: frozenset[str]
|
|
327
|
+
) -> frozenset[str]:
|
|
328
|
+
proven: frozenset[str] = frozenset()
|
|
329
|
+
while True:
|
|
330
|
+
nominal = frozenset(
|
|
331
|
+
declaration.name
|
|
332
|
+
for declaration in declarations
|
|
333
|
+
if not declaration.decorator_list
|
|
334
|
+
and not _unproven_metaclass_header(declaration, imports, unsafe_bindings)
|
|
335
|
+
and all(_nominal_base(base, imports, proven, unsafe_bindings) for base in declaration.bases)
|
|
336
|
+
)
|
|
337
|
+
if nominal <= proven:
|
|
338
|
+
return proven
|
|
339
|
+
proven |= nominal
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _nominal_base(
|
|
343
|
+
expression: ast.expr, imports: ImportIndex, local_classes: frozenset[str], unsafe_bindings: frozenset[str]
|
|
344
|
+
) -> bool:
|
|
345
|
+
if isinstance(expression, ast.Subscript):
|
|
346
|
+
return False
|
|
347
|
+
return _type_reference(expression, imports, local_classes, unsafe_bindings) is not None or (
|
|
348
|
+
_builtin_class_reference(expression, imports, unsafe_bindings) is not None
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _unproven_metaclass_header(
|
|
353
|
+
declaration: ast.ClassDef, imports: ImportIndex, unsafe_bindings: frozenset[str]
|
|
354
|
+
) -> bool:
|
|
355
|
+
for keyword in declaration.keywords:
|
|
356
|
+
if keyword.arg is None:
|
|
357
|
+
return True
|
|
358
|
+
if keyword.arg != "metaclass":
|
|
359
|
+
continue
|
|
360
|
+
if _builtin_class_reference(keyword.value, imports, unsafe_bindings) == "type":
|
|
361
|
+
continue
|
|
362
|
+
return True
|
|
363
|
+
return False
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _builtin_class_reference(expression: ast.expr, imports: ImportIndex, unsafe_bindings: frozenset[str]) -> str | None:
|
|
367
|
+
root = expression
|
|
368
|
+
while isinstance(root, ast.Attribute):
|
|
369
|
+
root = root.value
|
|
370
|
+
if not isinstance(root, ast.Name) or root.id in unsafe_bindings:
|
|
371
|
+
return None
|
|
372
|
+
if (
|
|
373
|
+
isinstance(expression, ast.Name)
|
|
374
|
+
and expression.id in _BUILTIN_TYPES
|
|
375
|
+
and imports.builtin_is_unshadowed(expression.id)
|
|
376
|
+
and f"builtins.{expression.id}" not in unsafe_bindings
|
|
377
|
+
):
|
|
378
|
+
return expression.id
|
|
379
|
+
symbol = imports.resolved_symbol(expression, sources=frozenset({"builtins"}))
|
|
380
|
+
if symbol in _BUILTIN_TYPES and f"builtins.{symbol}" not in unsafe_bindings:
|
|
381
|
+
return symbol
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _unsafe_local_bound_names(tree: ast.Module, all_nodes: tuple[ast.AST, ...], imports: ImportIndex) -> frozenset[str]:
|
|
386
|
+
names = _unsafe_import_bound_names(tree, all_nodes, imports)
|
|
324
387
|
names.update(
|
|
325
388
|
candidate.name
|
|
326
389
|
for candidate in all_nodes
|
|
@@ -334,6 +397,31 @@ def _unsafe_local_bound_names(tree: ast.Module, all_nodes: tuple[ast.AST, ...])
|
|
|
334
397
|
return frozenset(names)
|
|
335
398
|
|
|
336
399
|
|
|
400
|
+
def _unsafe_import_bound_names(tree: ast.Module, all_nodes: tuple[ast.AST, ...], imports: ImportIndex) -> set[str]:
|
|
401
|
+
module_imports = {id(statement) for statement in tree.body if isinstance(statement, (ast.Import, ast.ImportFrom))}
|
|
402
|
+
names: set[str] = set()
|
|
403
|
+
for statement in all_nodes:
|
|
404
|
+
if not isinstance(statement, (ast.Import, ast.ImportFrom)):
|
|
405
|
+
continue
|
|
406
|
+
relative = isinstance(statement, ast.ImportFrom) and statement.level > 0
|
|
407
|
+
if id(statement) in module_imports and not relative:
|
|
408
|
+
continue
|
|
409
|
+
for alias in statement.names:
|
|
410
|
+
if not _preserves_indexed_import(statement, alias, imports):
|
|
411
|
+
names.add(alias.asname or alias.name.partition(".")[0])
|
|
412
|
+
return names
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _preserves_indexed_import(statement: ast.Import | ast.ImportFrom, alias: ast.alias, imports: ImportIndex) -> bool:
|
|
416
|
+
local = alias.asname or alias.name.partition(".")[0]
|
|
417
|
+
target = imports.bindings.get(local)
|
|
418
|
+
if target is None:
|
|
419
|
+
return False
|
|
420
|
+
if isinstance(statement, ast.ImportFrom):
|
|
421
|
+
return not statement.level and target.module == statement.module and target.symbol == alias.name
|
|
422
|
+
return target.module == alias.name and target.symbol is None
|
|
423
|
+
|
|
424
|
+
|
|
337
425
|
def _ladder_findings(
|
|
338
426
|
all_nodes: tuple[ast.AST, ...],
|
|
339
427
|
path: Path,
|
|
@@ -632,6 +720,7 @@ def _type_reference(
|
|
|
632
720
|
expression.id in _BUILTIN_TYPES
|
|
633
721
|
and imports.builtin_is_unshadowed(expression.id)
|
|
634
722
|
and expression.id not in unsafe_bindings
|
|
723
|
+
and f"builtins.{expression.id}" not in unsafe_bindings
|
|
635
724
|
):
|
|
636
725
|
return expression.id
|
|
637
726
|
if expression.id in local_classes and expression.id not in unsafe_bindings:
|
|
@@ -116,7 +116,7 @@ class RequirePublicDependencyContract(ProjectRule):
|
|
|
116
116
|
if implementation is None:
|
|
117
117
|
return None
|
|
118
118
|
contract = _existing_substitutable_contract(implementation, project)
|
|
119
|
-
members = _dependency_members(node, fields)
|
|
119
|
+
members = _dependency_members(node, fields, context)
|
|
120
120
|
if contract is None or members is None or not members <= _public_operations(contract):
|
|
121
121
|
return None
|
|
122
122
|
return Diagnostic(
|
|
@@ -230,11 +230,15 @@ def _declaration(unit: SourceUnit, name: str) -> ast.ClassDef | None:
|
|
|
230
230
|
return next((node for node in unit.tree.body if isinstance(node, ast.ClassDef) and node.name == name), None)
|
|
231
231
|
|
|
232
232
|
|
|
233
|
-
def _dependency_members(
|
|
233
|
+
def _dependency_members(
|
|
234
|
+
node: ast.ClassDef, fields: frozenset[str], context: PythonFileContext
|
|
235
|
+
) -> frozenset[str] | None:
|
|
234
236
|
members: set[str] = set()
|
|
235
237
|
called = False
|
|
236
238
|
for method in class_methods(node):
|
|
237
239
|
for child in ast.walk(method):
|
|
240
|
+
if _dependency_field_escapes(child, fields, context):
|
|
241
|
+
return None
|
|
238
242
|
target = child.func if isinstance(child, ast.Call) else child
|
|
239
243
|
if (
|
|
240
244
|
isinstance(target, ast.Attribute)
|
|
@@ -246,3 +250,16 @@ def _dependency_members(node: ast.ClassDef, fields: frozenset[str]) -> frozenset
|
|
|
246
250
|
members.add(target.attr)
|
|
247
251
|
called |= isinstance(child, ast.Call) and not method.name.startswith("_")
|
|
248
252
|
return frozenset(members) if called else None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _dependency_field_escapes(node: ast.AST, fields: frozenset[str], context: PythonFileContext) -> bool:
|
|
256
|
+
if not (
|
|
257
|
+
isinstance(node, ast.Attribute)
|
|
258
|
+
and isinstance(node.ctx, ast.Load)
|
|
259
|
+
and isinstance(node.value, ast.Name)
|
|
260
|
+
and node.value.id == "self"
|
|
261
|
+
and node.attr in fields
|
|
262
|
+
):
|
|
263
|
+
return False
|
|
264
|
+
parent = context.parents.get(node)
|
|
265
|
+
return not (isinstance(parent, ast.Attribute) and parent.value is node)
|
|
@@ -38,15 +38,17 @@ class UnusedTestFactoryOption(Rule):
|
|
|
38
38
|
code = "SARJ443"
|
|
39
39
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
40
40
|
default_level=Severity.WARNING,
|
|
41
|
-
summary="A
|
|
41
|
+
summary="A private test factory exposes an invariant literal or unexercised local callable option.",
|
|
42
42
|
rationale="Unused customization obscures the values that actually distinguish test scenarios.",
|
|
43
|
-
remediation="Consider keeping the invariant
|
|
43
|
+
remediation="Consider keeping the invariant literal or stable local callable in the construction instead of exposing an unexercised option. Preserve intentional dependency contracts and callable default binding timing.",
|
|
44
44
|
category=RuleCategory.TESTING,
|
|
45
45
|
autofix=AutofixPolicy.NONE,
|
|
46
46
|
limitations=(
|
|
47
|
-
"
|
|
48
|
-
"
|
|
47
|
+
"Literal findings consider only private _make_ and _build_ helpers nested directly inside test functions, with at least two known direct callers and straight-line construction bodies.",
|
|
48
|
+
"Callable findings consider module-level private helpers with the same construction shape and an earlier undecorated local function default, only when at least two known direct callers all omit that option.",
|
|
49
|
+
"Conftest callable factories, explicit callable arguments, exports, decorators, escaping references, reflection, unpacking, rebinding, shadowing and ambiguous mutation or patch targets are excluded.",
|
|
49
50
|
"Same-name declarations elsewhere in the file conservatively exclude a helper. No autofix: this warning identifies unexercised customization, not an invalid callable contract.",
|
|
51
|
+
"Callable findings describe known callers in this file; cross-module and dynamic callers are not inferred. Retain intentional extension points with a reasoned exception, preserving definition-time default capture.",
|
|
50
52
|
),
|
|
51
53
|
examples=(
|
|
52
54
|
RuleExample(
|
|
@@ -63,6 +65,40 @@ class UnusedTestFactoryOption(Rule):
|
|
|
63
65
|
expected_count=1,
|
|
64
66
|
public=True,
|
|
65
67
|
),
|
|
68
|
+
RuleExample(
|
|
69
|
+
example_id="unused-local-callable-option",
|
|
70
|
+
scenario="callable-default",
|
|
71
|
+
title="Known callers do not replace a stable local dependency",
|
|
72
|
+
outcome=ExampleOutcome.MATCH,
|
|
73
|
+
files=(
|
|
74
|
+
ExampleFile.python(
|
|
75
|
+
"tests/test_widget.py",
|
|
76
|
+
"def _read():\n return 'ready'\n"
|
|
77
|
+
"def _make_widget(*, read=_read):\n return Widget(read=read)\n"
|
|
78
|
+
"_make_widget()\n_make_widget()\n",
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
focus_path=PurePosixPath("tests/test_widget.py"),
|
|
82
|
+
expected_count=1,
|
|
83
|
+
public=True,
|
|
84
|
+
),
|
|
85
|
+
RuleExample(
|
|
86
|
+
example_id="exercised-local-callable-option",
|
|
87
|
+
scenario="callable-default",
|
|
88
|
+
title="An explicit callback preserves the dependency contract",
|
|
89
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
90
|
+
files=(
|
|
91
|
+
ExampleFile.python(
|
|
92
|
+
"tests/test_widget.py",
|
|
93
|
+
"def _read():\n return 'ready'\n"
|
|
94
|
+
"def _make_widget(*, read=_read):\n return Widget(read=read)\n"
|
|
95
|
+
"_make_widget()\n_make_widget(read=_read)\n",
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
focus_path=PurePosixPath("tests/test_widget.py"),
|
|
99
|
+
expected_count=0,
|
|
100
|
+
public=True,
|
|
101
|
+
),
|
|
66
102
|
RuleExample(
|
|
67
103
|
example_id="exercised-size-option",
|
|
68
104
|
title="Keep customization that a caller exercises",
|
|
@@ -96,14 +132,15 @@ class UnusedTestFactoryOption(Rule):
|
|
|
96
132
|
lines = context.source_lines
|
|
97
133
|
findings: list[Diagnostic] = []
|
|
98
134
|
for function in context.nodes(ast.FunctionDef):
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
not _is_factory(function)
|
|
102
|
-
or not isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
103
|
-
or not owner.name.startswith("test_")
|
|
104
|
-
):
|
|
135
|
+
if not _is_factory(function):
|
|
105
136
|
continue
|
|
106
|
-
|
|
137
|
+
owner = context.parents.get(function)
|
|
138
|
+
if isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef) and owner.name.startswith("test_"):
|
|
139
|
+
findings.extend(
|
|
140
|
+
_factory_findings(tree, function, path, lines, self.code, node_index=context.node_index)
|
|
141
|
+
)
|
|
142
|
+
elif owner is tree and path.name != "conftest.py":
|
|
143
|
+
findings.extend(_callable_factory_findings(context, function, self.code))
|
|
107
144
|
return sorted(findings, key=lambda item: (item.line, item.col))
|
|
108
145
|
|
|
109
146
|
|
|
@@ -280,3 +317,79 @@ def _factory_options(function: ast.FunctionDef) -> list[tuple[ast.arg, ast.expr]
|
|
|
280
317
|
]
|
|
281
318
|
used = {node.id for node in nodes(function, ast.Name) if isinstance(node.ctx, ast.Load)}
|
|
282
319
|
return [(argument, default) for argument, default in defaults if argument.arg in used and default is not None]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _callable_factory_findings(context: PythonFileContext, function: ast.FunctionDef, code: str) -> list[Diagnostic]:
|
|
323
|
+
tree = context.tree
|
|
324
|
+
if tree is None:
|
|
325
|
+
return []
|
|
326
|
+
options = [(argument, default) for argument, default in _factory_options(function) if isinstance(default, ast.Name)]
|
|
327
|
+
if not options:
|
|
328
|
+
return []
|
|
329
|
+
calls = _direct_calls(tree, function, node_index=context.node_index)
|
|
330
|
+
if len(calls) < _MIN_INVARIANT_CALLS:
|
|
331
|
+
return []
|
|
332
|
+
bound = _bound_calls(function, calls)
|
|
333
|
+
if bound is None:
|
|
334
|
+
return []
|
|
335
|
+
supplied = {name for arguments in bound for name in arguments}
|
|
336
|
+
rebound = {node.id for node in nodes(function, ast.Name) if not isinstance(node.ctx, ast.Load)}
|
|
337
|
+
findings: list[Diagnostic] = []
|
|
338
|
+
for argument, default in options:
|
|
339
|
+
if (
|
|
340
|
+
argument.arg in supplied | rebound
|
|
341
|
+
or not _is_stable_local_callable(context, function, default.id)
|
|
342
|
+
or is_suppressed(context.source_lines, argument.lineno, code)
|
|
343
|
+
):
|
|
344
|
+
continue
|
|
345
|
+
findings.append(
|
|
346
|
+
Diagnostic(
|
|
347
|
+
path=context.path,
|
|
348
|
+
line=argument.lineno,
|
|
349
|
+
col=argument.col_offset + 1,
|
|
350
|
+
code=code,
|
|
351
|
+
severity=Severity.WARNING,
|
|
352
|
+
message=(
|
|
353
|
+
f"No known direct caller in this file supplies `{function.name}.{argument.arg}`; consider "
|
|
354
|
+
f"using the stable local callable `{default.id}` internally. Preserve default binding timing "
|
|
355
|
+
"and retain the option if external callers or an intentional test contract need it."
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
return findings
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _is_stable_local_callable(context: PythonFileContext, factory: ast.FunctionDef, name: str) -> bool:
|
|
363
|
+
tree = context.tree
|
|
364
|
+
if tree is None or not any(
|
|
365
|
+
isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
366
|
+
and node.name == name
|
|
367
|
+
and node.lineno < factory.lineno
|
|
368
|
+
and not node.decorator_list
|
|
369
|
+
for node in tree.body
|
|
370
|
+
):
|
|
371
|
+
return False
|
|
372
|
+
if not _has_unambiguous_factory_name(tree, name, node_index=context.node_index):
|
|
373
|
+
return False
|
|
374
|
+
return not (
|
|
375
|
+
any(node.id == name and not isinstance(node.ctx, ast.Load) for node in context.nodes(ast.Name))
|
|
376
|
+
or any(
|
|
377
|
+
not isinstance(node.ctx, ast.Load) and any(child.id == name for child in nodes(node.value, ast.Name))
|
|
378
|
+
for node in context.nodes(ast.Attribute, ast.Subscript)
|
|
379
|
+
)
|
|
380
|
+
or any(
|
|
381
|
+
isinstance(node.value, str) and (name in node.value.split(".") or factory.name in node.value.split("."))
|
|
382
|
+
for node in context.nodes(ast.Constant)
|
|
383
|
+
)
|
|
384
|
+
or _provider_escapes(context, name)
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _provider_escapes(context: PythonFileContext, name: str) -> bool:
|
|
389
|
+
for node in context.nodes(ast.Name):
|
|
390
|
+
if node.id != name or not isinstance(node.ctx, ast.Load):
|
|
391
|
+
continue
|
|
392
|
+
parent = context.parents.get(node)
|
|
393
|
+
if not (isinstance(parent, ast.arguments) or (isinstance(parent, ast.Call) and parent.func is node)):
|
|
394
|
+
return True
|
|
395
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/_test_composition.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
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/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
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_dunder_all.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
|
|
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.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/no_select_star.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
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_class_row.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
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_or_pattern.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.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/prefer_str_enum.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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.16.2 → sarj_python_lint-1.16.4}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|