sarj-python-lint 1.45.0__tar.gz → 1.45.2__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.45.0 → sarj_python_lint-1.45.2}/PKG-INFO +1 -1
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/pyproject.toml +1 -1
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/__main__.py +18 -4
- sarj_python_lint-1.45.2/src/sarj_python_lint/json_boundary.py +29 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/ratchet.py +1 -1
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_tautological_expect.py +52 -17
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +58 -54
- sarj_python_lint-1.45.0/src/sarj_python_lint/json_boundary.py +0 -10
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/.gitignore +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/LICENSE +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/README.md +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/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.45.
|
|
3
|
+
Version: 1.45.2
|
|
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/
|
|
@@ -430,12 +430,26 @@ def _baseline_path(path: Path, *, root: Path | None = None) -> str:
|
|
|
430
430
|
def _read_baseline(path: Path) -> dict[str, dict[str, int]]:
|
|
431
431
|
raw = parse_json(path.read_text(encoding="utf-8"))
|
|
432
432
|
if not is_object_mapping(raw):
|
|
433
|
-
|
|
433
|
+
msg = "baseline must be an object of file paths and diagnostic counts"
|
|
434
|
+
raise ValueError(msg)
|
|
434
435
|
counts: dict[str, dict[str, int]] = {}
|
|
435
436
|
for file_key, per_code in raw.items():
|
|
436
|
-
if not isinstance(file_key, str) or not is_object_mapping(per_code):
|
|
437
|
-
|
|
438
|
-
|
|
437
|
+
if not isinstance(file_key, str) or not file_key or not is_object_mapping(per_code):
|
|
438
|
+
msg = "baseline entries must map non-empty file paths to diagnostic counts"
|
|
439
|
+
raise ValueError(msg)
|
|
440
|
+
validated: dict[str, int] = {}
|
|
441
|
+
for code, count in per_code.items():
|
|
442
|
+
if (
|
|
443
|
+
not isinstance(code, str)
|
|
444
|
+
or not code
|
|
445
|
+
or not isinstance(count, int)
|
|
446
|
+
or isinstance(count, bool)
|
|
447
|
+
or count < 0
|
|
448
|
+
):
|
|
449
|
+
msg = "baseline counts must map non-empty diagnostic codes to non-negative integers"
|
|
450
|
+
raise ValueError(msg)
|
|
451
|
+
validated[code] = count
|
|
452
|
+
counts[file_key] = validated
|
|
439
453
|
return counts
|
|
440
454
|
|
|
441
455
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import TypeIs
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_json(text: str) -> object:
|
|
6
|
+
try:
|
|
7
|
+
return json.loads(text, object_pairs_hook=_unique_pairs, parse_constant=_invalid_constant) # pyright: ignore[reportAny] -- untyped stdlib parser boundary.
|
|
8
|
+
except RecursionError as error:
|
|
9
|
+
message = "JSON nesting exceeds the supported depth"
|
|
10
|
+
raise ValueError(message) from error
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _unique_pairs(pairs: list[tuple[str, object]]) -> dict[str, object]:
|
|
14
|
+
result: dict[str, object] = {}
|
|
15
|
+
for key, value in pairs:
|
|
16
|
+
if key in result:
|
|
17
|
+
message = f"duplicate JSON key: {key}"
|
|
18
|
+
raise ValueError(message)
|
|
19
|
+
result[key] = value
|
|
20
|
+
return result
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _invalid_constant(value: str) -> None:
|
|
24
|
+
message = f"non-finite JSON constant: {value}"
|
|
25
|
+
raise ValueError(message)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_object_mapping(value: object) -> TypeIs[dict[object, object]]:
|
|
29
|
+
return isinstance(value, dict)
|
|
@@ -221,7 +221,7 @@ def seed(measurement: Measurement, baseline: Baseline) -> Baseline:
|
|
|
221
221
|
def load_baseline(path: Path) -> Baseline:
|
|
222
222
|
raw = parse_json(path.read_text(encoding="utf-8"))
|
|
223
223
|
schema = _get(raw, "schema_version")
|
|
224
|
-
if schema is not None and schema != BASELINE_SCHEMA_VERSION:
|
|
224
|
+
if schema is not None and (type(schema) is not int or schema != BASELINE_SCHEMA_VERSION):
|
|
225
225
|
msg = f"unsupported suppression baseline schema_version: {schema!r}"
|
|
226
226
|
raise ValueError(msg)
|
|
227
227
|
files = _get(raw, "files")
|
|
@@ -56,6 +56,7 @@ class NoTautologicalExpect(Rule):
|
|
|
56
56
|
"Detection covers truthy scalar constants, definitely non-empty list/set/dict displays, and directly asserted generator expressions or lambdas.",
|
|
57
57
|
"Generator consumption, lambda invocation, comprehensions, and runtime iterator/callable values are not inferred; empty universal-quantification contracts remain valid.",
|
|
58
58
|
"Test-local immutable scalar bindings and their aliases are tracked only through straight-line assignments; calls, control flow, mutable values, and global/nonlocal writes invalidate tracking.",
|
|
59
|
+
"Pure Boolean conditions, compatible numeric/string/bytes ordering and chained comparisons over known scalar setup are evaluated; proven-true pure assertions retain bindings because their messages do not execute.",
|
|
59
60
|
"Ruff owns asserted strings and tuples, literal comparisons and identity, and unittest-style assertion methods.",
|
|
60
61
|
"Generated files, always-failing assertions, deliberate match-arm success markers, and runtime-value comparisons are excluded.",
|
|
61
62
|
),
|
|
@@ -196,12 +197,13 @@ def _function_bound_tautologies(function: ast.FunctionDef | ast.AsyncFunctionDef
|
|
|
196
197
|
for statement in function.body:
|
|
197
198
|
if _record_scalar_assignment(statement, bindings):
|
|
198
199
|
continue
|
|
199
|
-
if isinstance(statement, ast.Assert):
|
|
200
|
+
if isinstance(statement, ast.Assert) and bindings and _scalar_condition(statement.test, bindings) is True:
|
|
200
201
|
references_setup = any(
|
|
201
202
|
isinstance(node, ast.Name) and node.id in bindings for node in ast.walk(statement.test)
|
|
202
203
|
)
|
|
203
|
-
if references_setup
|
|
204
|
+
if references_setup:
|
|
204
205
|
found.append(_Tautology(statement, "the condition follows only from unchanged scalar setup"))
|
|
206
|
+
continue
|
|
205
207
|
bindings.clear()
|
|
206
208
|
return found
|
|
207
209
|
|
|
@@ -239,26 +241,59 @@ def _scalar_value(node: ast.expr, bindings: dict[str, object]) -> object:
|
|
|
239
241
|
|
|
240
242
|
|
|
241
243
|
def _scalar_condition(node: ast.expr, bindings: dict[str, object]) -> bool | None:
|
|
242
|
-
if isinstance(node, ast.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if left is _UNKNOWN or right is _UNKNOWN:
|
|
244
|
+
if isinstance(node, ast.BoolOp):
|
|
245
|
+
values = [_scalar_condition(value, bindings) for value in node.values]
|
|
246
|
+
if None in values:
|
|
246
247
|
return None
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return left is not right
|
|
256
|
-
case _:
|
|
257
|
-
return None
|
|
248
|
+
return all(values) if isinstance(node.op, ast.And) else any(values)
|
|
249
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
|
|
250
|
+
value = _scalar_condition(node.operand, bindings)
|
|
251
|
+
return None if value is None else not value
|
|
252
|
+
if isinstance(node, ast.Compare):
|
|
253
|
+
values = [_scalar_value(value, bindings) for value in (node.left, *node.comparators)]
|
|
254
|
+
results = list(map(_scalar_relation, values[:-1], values[1:], node.ops, strict=True))
|
|
255
|
+
return None if None in results else all(results)
|
|
258
256
|
value = _scalar_value(node, bindings)
|
|
259
257
|
return None if value is _UNKNOWN else bool(value)
|
|
260
258
|
|
|
261
259
|
|
|
260
|
+
def _scalar_relation(left: object, right: object, op: ast.cmpop) -> bool | None:
|
|
261
|
+
if left is _UNKNOWN or right is _UNKNOWN:
|
|
262
|
+
return None
|
|
263
|
+
match op:
|
|
264
|
+
case ast.Eq():
|
|
265
|
+
return left == right
|
|
266
|
+
case ast.NotEq():
|
|
267
|
+
return left != right
|
|
268
|
+
case ast.Is() if left is None or right is None or isinstance(left, bool) or isinstance(right, bool):
|
|
269
|
+
return left is right
|
|
270
|
+
case ast.IsNot() if left is None or right is None or isinstance(left, bool) or isinstance(right, bool):
|
|
271
|
+
return left is not right
|
|
272
|
+
case _:
|
|
273
|
+
pass
|
|
274
|
+
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
275
|
+
return _ordered_relation(left, right, op)
|
|
276
|
+
if isinstance(left, str) and isinstance(right, str):
|
|
277
|
+
return _ordered_relation(left, right, op)
|
|
278
|
+
if isinstance(left, bytes) and isinstance(right, bytes):
|
|
279
|
+
return _ordered_relation(left, right, op)
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _ordered_relation[T: (int | float, str, bytes)](left: T, right: T, op: ast.cmpop) -> bool | None:
|
|
284
|
+
match op:
|
|
285
|
+
case ast.Lt():
|
|
286
|
+
return left < right
|
|
287
|
+
case ast.LtE():
|
|
288
|
+
return left <= right
|
|
289
|
+
case ast.Gt():
|
|
290
|
+
return left > right
|
|
291
|
+
case ast.GtE():
|
|
292
|
+
return left >= right
|
|
293
|
+
case _:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
262
297
|
def _exempt_nodes(tree: ast.Module, *, node_index: NodeIndex | None = None) -> set[ast.AST]:
|
|
263
298
|
exempt: set[ast.AST] = set()
|
|
264
299
|
for node in walk_ast(tree, index=node_index):
|
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from collections import Counter
|
|
5
|
+
from functools import cached_property
|
|
5
6
|
from pathlib import PurePosixPath
|
|
6
|
-
from typing import TYPE_CHECKING, ClassVar, NamedTuple, override
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
|
|
7
8
|
|
|
8
9
|
from sarj_python_lint.rule_base import (
|
|
9
10
|
AutofixPolicy,
|
|
@@ -43,6 +44,34 @@ class _HelperImport(NamedTuple):
|
|
|
43
44
|
level: int
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
@final
|
|
48
|
+
class _ModuleFacts:
|
|
49
|
+
def __init__(self, tree: ast.Module) -> None:
|
|
50
|
+
self.tree = tree
|
|
51
|
+
|
|
52
|
+
@cached_property
|
|
53
|
+
def bindings(self) -> Counter[str]:
|
|
54
|
+
return _scope_bindings(self.tree)
|
|
55
|
+
|
|
56
|
+
@cached_property
|
|
57
|
+
def writes(self) -> tuple[ast.Attribute | ast.Subscript, ...]:
|
|
58
|
+
return tuple(
|
|
59
|
+
node
|
|
60
|
+
for node in ast.walk(self.tree)
|
|
61
|
+
if isinstance(node, (ast.Attribute, ast.Subscript)) and isinstance(node.ctx, (ast.Store, ast.Del))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@cached_property
|
|
65
|
+
def mutated_roots(self) -> frozenset[str]:
|
|
66
|
+
return frozenset(root for node in self.writes if (root := _root_name(node.value)) is not None)
|
|
67
|
+
|
|
68
|
+
@cached_property
|
|
69
|
+
def references(self) -> frozenset[str]:
|
|
70
|
+
return frozenset(
|
|
71
|
+
node.id for node in ast.walk(self.tree) if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
46
75
|
class PreferPytestFixtureInjection(Rule):
|
|
47
76
|
id: str = "prefer-pytest-fixture-injection"
|
|
48
77
|
code: str = "SARJ476"
|
|
@@ -70,6 +99,7 @@ class PreferPytestFixtureInjection(Rule):
|
|
|
70
99
|
"Async pools require an explicit literal open=False, no unpacked arguments, and no calls inside constructor arguments; opened or dynamically configured async pools need lifecycle-specific fixture design.",
|
|
71
100
|
"Setup must be the first executable statement and immediately feed a plain call, or appear inline as a direct argument of that first call.",
|
|
72
101
|
"Class tests, decorated helpers, unknown test decorators, dynamic imports, re-exports, and ambiguous or out-of-bound source paths are excluded.",
|
|
102
|
+
"Tests with statically visible callable references in any scope are excluded; scope-ambiguous references are handled conservatively.",
|
|
73
103
|
"Command-line conftest discovery overrides and external pytest plugin registrations are not inferred.",
|
|
74
104
|
),
|
|
75
105
|
examples=(
|
|
@@ -167,24 +197,23 @@ class PreferPytestFixtureInjection(Rule):
|
|
|
167
197
|
):
|
|
168
198
|
return []
|
|
169
199
|
tree = context.tree
|
|
170
|
-
|
|
200
|
+
facts = _ModuleFacts(tree)
|
|
201
|
+
helpers = _resource_helpers(context, facts)
|
|
171
202
|
if not helpers:
|
|
172
203
|
return []
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
isinstance(node, ast.Attribute) and node.attr == "__test__" and isinstance(node.ctx, (ast.Store, ast.Del))
|
|
176
|
-
for node in ast.walk(tree)
|
|
204
|
+
if "__test__" in facts.bindings or any(
|
|
205
|
+
isinstance(node, ast.Attribute) and node.attr == "__test__" for node in facts.writes
|
|
177
206
|
):
|
|
178
207
|
return []
|
|
179
208
|
diagnostics: list[Diagnostic] = []
|
|
180
209
|
for statement in tree.body:
|
|
181
210
|
if (
|
|
182
211
|
not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
183
|
-
or
|
|
184
|
-
or
|
|
212
|
+
or facts.bindings[statement.name] != 1
|
|
213
|
+
or statement.name in facts.references
|
|
185
214
|
):
|
|
186
215
|
continue
|
|
187
|
-
call = _setup_call(context, statement, helpers)
|
|
216
|
+
call = _setup_call(context, statement, helpers, facts)
|
|
188
217
|
if call is not None:
|
|
189
218
|
diagnostics.append(
|
|
190
219
|
Diagnostic(
|
|
@@ -205,17 +234,16 @@ class PreferPytestFixtureInjection(Rule):
|
|
|
205
234
|
return diagnostics
|
|
206
235
|
|
|
207
236
|
|
|
208
|
-
def _resource_helpers(context: PythonFileContext) -> frozenset[str]:
|
|
237
|
+
def _resource_helpers(context: PythonFileContext, facts: _ModuleFacts) -> frozenset[str]:
|
|
209
238
|
tree = context.tree
|
|
210
239
|
if tree is None:
|
|
211
240
|
return frozenset()
|
|
212
241
|
references = tuple(_helper_imports(tree))
|
|
213
242
|
if not references:
|
|
214
243
|
return frozenset()
|
|
215
|
-
bindings = _scope_bindings(tree)
|
|
216
244
|
helpers: set[str] = set()
|
|
217
245
|
for reference in references:
|
|
218
|
-
if bindings[reference.local] != 1 or
|
|
246
|
+
if facts.bindings[reference.local] != 1 or reference.local in facts.mutated_roots:
|
|
219
247
|
continue
|
|
220
248
|
target = context.session.local_source.resolve_module(
|
|
221
249
|
context.path, reference.module, reference.level, facts=context.session.first_party
|
|
@@ -249,11 +277,12 @@ def _helper_imports(tree: ast.Module) -> Iterator[_HelperImport]:
|
|
|
249
277
|
|
|
250
278
|
|
|
251
279
|
def _is_resource_helper(module: LocalModule, symbol: str) -> bool:
|
|
280
|
+
facts = _ModuleFacts(module.tree)
|
|
252
281
|
if (
|
|
253
282
|
is_generated(module.path, module.source)
|
|
254
|
-
or
|
|
283
|
+
or facts.bindings[symbol] != 1
|
|
255
284
|
or _has_wildcard(module.tree)
|
|
256
|
-
or
|
|
285
|
+
or symbol in facts.mutated_roots
|
|
257
286
|
):
|
|
258
287
|
return False
|
|
259
288
|
helper = next(
|
|
@@ -275,8 +304,8 @@ def _is_resource_helper(module: LocalModule, symbol: str) -> bool:
|
|
|
275
304
|
qualified = module.runtime_imports.resolved_qualified_name(constructor)
|
|
276
305
|
return (
|
|
277
306
|
root is not None
|
|
278
|
-
and
|
|
279
|
-
and not
|
|
307
|
+
and facts.bindings[root] == 1
|
|
308
|
+
and root not in facts.mutated_roots
|
|
280
309
|
and qualified in _POOL_CONSTRUCTORS
|
|
281
310
|
and (qualified != "psycopg_pool.AsyncConnectionPool" or _unopened_async_allocation(allocation))
|
|
282
311
|
)
|
|
@@ -294,9 +323,11 @@ def _unopened_async_allocation(allocation: ast.Call) -> bool:
|
|
|
294
323
|
)
|
|
295
324
|
|
|
296
325
|
|
|
297
|
-
def _setup_call(
|
|
326
|
+
def _setup_call(
|
|
327
|
+
context: PythonFileContext, function: Function, helpers: frozenset[str], facts: _ModuleFacts
|
|
328
|
+
) -> ast.Call | None:
|
|
298
329
|
if not function.name.startswith("test_") or any(
|
|
299
|
-
not _pytest_mark(context, decorator) for decorator in function.decorator_list
|
|
330
|
+
not _pytest_mark(context, decorator, facts) for decorator in function.decorator_list
|
|
300
331
|
):
|
|
301
332
|
return None
|
|
302
333
|
bindings = _scope_bindings(function)
|
|
@@ -313,21 +344,21 @@ def _setup_call(context: PythonFileContext, function: Function, helpers: frozens
|
|
|
313
344
|
if setup is None:
|
|
314
345
|
return None
|
|
315
346
|
if _helper_call(setup, available):
|
|
316
|
-
return _assigned_dependency_call(
|
|
317
|
-
if not _unshadowed_consumer(
|
|
347
|
+
return _assigned_dependency_call(body, bindings, setup, facts)
|
|
348
|
+
if not _unshadowed_consumer(setup, bindings, facts):
|
|
318
349
|
return None
|
|
319
350
|
inline = [arg for arg in _arguments(setup) if isinstance(arg, ast.Call) and _helper_call(arg, available)]
|
|
320
351
|
return inline[0] if len(inline) == 1 else None
|
|
321
352
|
|
|
322
353
|
|
|
323
354
|
def _assigned_dependency_call(
|
|
324
|
-
|
|
355
|
+
body: list[ast.stmt], bindings: Counter[str], setup: ast.Call, facts: _ModuleFacts
|
|
325
356
|
) -> ast.Call | None:
|
|
326
357
|
resource = _assignment_name(body[0])
|
|
327
358
|
if resource is None or bindings[resource] != 1 or len(body) < _SETUP_AND_CONSUMER:
|
|
328
359
|
return None
|
|
329
360
|
consumer = _plain_call(body[1])
|
|
330
|
-
if consumer is None or not _unshadowed_consumer(
|
|
361
|
+
if consumer is None or not _unshadowed_consumer(consumer, bindings, facts):
|
|
331
362
|
return None
|
|
332
363
|
for argument in _arguments(consumer):
|
|
333
364
|
if isinstance(argument, ast.Name) and argument.id == resource:
|
|
@@ -335,7 +366,7 @@ def _assigned_dependency_call(
|
|
|
335
366
|
return None
|
|
336
367
|
|
|
337
368
|
|
|
338
|
-
def _pytest_mark(context: PythonFileContext, decorator: ast.expr) -> bool:
|
|
369
|
+
def _pytest_mark(context: PythonFileContext, decorator: ast.expr, facts: _ModuleFacts) -> bool:
|
|
339
370
|
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
340
371
|
qualified = context.module_imports.resolved_qualified_name(target)
|
|
341
372
|
tree = context.tree
|
|
@@ -345,12 +376,10 @@ def _pytest_mark(context: PythonFileContext, decorator: ast.expr) -> bool:
|
|
|
345
376
|
or not qualified.startswith("pytest.mark.")
|
|
346
377
|
or tree is None
|
|
347
378
|
or root is None
|
|
348
|
-
or
|
|
379
|
+
or facts.bindings[root] != 1
|
|
349
380
|
):
|
|
350
381
|
return False
|
|
351
|
-
for node in
|
|
352
|
-
if not isinstance(node, (ast.Attribute, ast.Subscript)) or not isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
353
|
-
continue
|
|
382
|
+
for node in facts.writes:
|
|
354
383
|
if isinstance(node, ast.Subscript) and _root_name(node.value) == root:
|
|
355
384
|
return False
|
|
356
385
|
changed = context.module_imports.resolved_qualified_name(node)
|
|
@@ -359,25 +388,9 @@ def _pytest_mark(context: PythonFileContext, decorator: ast.expr) -> bool:
|
|
|
359
388
|
return True
|
|
360
389
|
|
|
361
390
|
|
|
362
|
-
def _unshadowed_consumer(
|
|
391
|
+
def _unshadowed_consumer(call: ast.Call, bindings: Counter[str], facts: _ModuleFacts) -> bool:
|
|
363
392
|
root = _root_name(call.func)
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
root is not None
|
|
367
|
-
and root not in bindings
|
|
368
|
-
and tree is not None
|
|
369
|
-
and _scope_bindings(tree)[root] <= 1
|
|
370
|
-
and not _root_mutated(tree, root)
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
def _module_reference(tree: ast.Module, name: str) -> bool:
|
|
375
|
-
return any(
|
|
376
|
-
isinstance(node, ast.Name) and node.id == name and isinstance(node.ctx, ast.Load)
|
|
377
|
-
for statement in tree.body
|
|
378
|
-
if not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
|
|
379
|
-
for node in ast.walk(statement)
|
|
380
|
-
)
|
|
393
|
+
return root is not None and root not in bindings and facts.bindings[root] <= 1 and root not in facts.mutated_roots
|
|
381
394
|
|
|
382
395
|
|
|
383
396
|
def _helper_call(call: ast.Call, helpers: frozenset[str] | set[str]) -> bool:
|
|
@@ -509,12 +522,3 @@ def _has_wildcard(tree: ast.Module) -> bool:
|
|
|
509
522
|
return any(
|
|
510
523
|
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names) for node in ast.walk(tree)
|
|
511
524
|
)
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
def _root_mutated(tree: ast.Module, root: str) -> bool:
|
|
515
|
-
return any(
|
|
516
|
-
isinstance(node, (ast.Attribute, ast.Subscript))
|
|
517
|
-
and isinstance(node.ctx, (ast.Store, ast.Del))
|
|
518
|
-
and _root_name(node.value) == root
|
|
519
|
-
for node in ast.walk(tree)
|
|
520
|
-
)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import TypeIs
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def parse_json(text: str) -> object:
|
|
6
|
-
return json.loads(text) # pyright: ignore[reportAny] -- untyped stdlib parser boundary.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def is_object_mapping(value: object) -> TypeIs[dict[object, object]]:
|
|
10
|
-
return isinstance(value, dict)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/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.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/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
|
|
File without changes
|
{sarj_python_lint-1.45.0 → sarj_python_lint-1.45.2}/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.45.0 → sarj_python_lint-1.45.2}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|