sarj-python-lint 1.8.0__tar.gz → 1.8.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.8.0 → sarj_python_lint-1.8.2}/PKG-INFO +2 -2
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/pyproject.toml +2 -2
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +124 -74
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +10 -62
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/.gitignore +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/LICENSE +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/README.md +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.8.0 → sarj_python_lint-1.8.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.8.
|
|
3
|
+
Version: 1.8.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/
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: packaging==26.3
|
|
21
21
|
Requires-Dist: ruff>=0.16.9
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.2.0
|
|
23
23
|
Requires-Dist: sqlglot==30.19.0
|
|
24
24
|
Requires-Dist: typer==0.27.2
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.8.
|
|
3
|
+
version = "1.8.2"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["sarj-rule-contracts==0.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.2.0", "packaging==26.3", "ruff>=0.16.9", "sqlglot==30.19.0", "typer==0.27.2"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
+
from enum import Enum, auto
|
|
4
5
|
from pathlib import PurePosixPath
|
|
5
6
|
from types import MappingProxyType
|
|
6
7
|
from typing import TYPE_CHECKING, ClassVar, Final, final, override
|
|
@@ -65,12 +66,18 @@ type _TestFunction = ast.FunctionDef | ast.AsyncFunctionDef
|
|
|
65
66
|
type _Reference = tuple[str, ...]
|
|
66
67
|
|
|
67
68
|
|
|
69
|
+
class _Evidence(Enum):
|
|
70
|
+
POSITIVE = auto()
|
|
71
|
+
INSUFFICIENT = auto()
|
|
72
|
+
UNRESOLVED = auto()
|
|
73
|
+
|
|
74
|
+
|
|
68
75
|
@final
|
|
69
76
|
class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
70
77
|
id = "async-mock-call-without-await-assertion"
|
|
71
78
|
code = "SARJ456"
|
|
72
79
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
73
|
-
default_level=Severity.
|
|
80
|
+
default_level=Severity.WARNING,
|
|
74
81
|
summary="Pair positive AsyncMock call assertions with evidence that the mock was awaited.",
|
|
75
82
|
rationale=(
|
|
76
83
|
"Calling an AsyncMock records a call before its coroutine is awaited. A positive call assertion can "
|
|
@@ -87,33 +94,50 @@ class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
|
87
94
|
"Only direct statements in collected test functions and methods are inspected; fixtures, helpers, nested blocks, and generated files are excluded.",
|
|
88
95
|
"Requires a fresh local unittest.mock.AsyncMock constructor with unshadowed imports; aliases, reassignment, reset, and ambiguous configuration are excluded.",
|
|
89
96
|
"Only the mock itself and unchanged immediate children of unspecced, unwrapped mocks are inferred; spec children, return values, and deeper chains are excluded.",
|
|
90
|
-
"Positive call
|
|
91
|
-
"
|
|
97
|
+
"Positive call evidence includes direct assertion methods and literal integer or boolean call-state checks; unresolved call expectations are excluded.",
|
|
98
|
+
"Await evidence excludes provably vacuous checks, including zero or nonnegative counts and empty expected await sequences; unresolved expectations are accepted without inferring their values.",
|
|
99
|
+
"Conjunctions need one accepted await branch; disjunctions need accepted evidence for the same mock in every branch. Argument equality and assertion strength are not compared.",
|
|
100
|
+
"Compound negation and custom expression semantics remain unresolved; no variable values or fixture graphs are inferred.",
|
|
92
101
|
"Custom assertion helpers and intentional scheduling contracts may require a reasoned local suppression.",
|
|
93
102
|
),
|
|
94
103
|
examples=(
|
|
95
104
|
RuleExample(
|
|
96
|
-
example_id="
|
|
97
|
-
title="A
|
|
105
|
+
example_id="call-only-async-mock",
|
|
106
|
+
title="A call assertion does not prove awaiting",
|
|
98
107
|
outcome=ExampleOutcome.MATCH,
|
|
99
108
|
files=(
|
|
100
109
|
ExampleFile.python(
|
|
101
110
|
"tests/test_delivery.py",
|
|
102
|
-
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n
|
|
111
|
+
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n send.assert_called_once_with('item')\n",
|
|
103
112
|
),
|
|
104
113
|
),
|
|
105
114
|
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
106
115
|
expected_count=1,
|
|
107
|
-
public=
|
|
116
|
+
public=True,
|
|
108
117
|
),
|
|
109
118
|
RuleExample(
|
|
110
|
-
example_id="call-
|
|
111
|
-
title="
|
|
119
|
+
example_id="call-and-await-async-mock",
|
|
120
|
+
title="Call and await assertions protect different contracts",
|
|
121
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
122
|
+
files=(
|
|
123
|
+
ExampleFile.python(
|
|
124
|
+
"tests/test_delivery.py",
|
|
125
|
+
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n send.assert_called_once_with('item')\n send.assert_awaited_once_with('item')\n",
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
129
|
+
expected_count=0,
|
|
130
|
+
public=True,
|
|
131
|
+
),
|
|
132
|
+
RuleExample(
|
|
133
|
+
example_id="vacuous-await-count",
|
|
134
|
+
scenario="await-expectation-boundaries",
|
|
135
|
+
title="A nonnegative await count does not prove execution",
|
|
112
136
|
outcome=ExampleOutcome.MATCH,
|
|
113
137
|
files=(
|
|
114
138
|
ExampleFile.python(
|
|
115
139
|
"tests/test_delivery.py",
|
|
116
|
-
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n send.
|
|
140
|
+
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n assert send.call_count == 1\n assert send.await_count >= 0\n",
|
|
117
141
|
),
|
|
118
142
|
),
|
|
119
143
|
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
@@ -121,13 +145,14 @@ class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
|
121
145
|
public=True,
|
|
122
146
|
),
|
|
123
147
|
RuleExample(
|
|
124
|
-
example_id="
|
|
125
|
-
|
|
148
|
+
example_id="dynamic-expected-awaits",
|
|
149
|
+
scenario="await-expectation-boundaries",
|
|
150
|
+
title="Unresolved await expectations retain their acceptance",
|
|
126
151
|
outcome=ExampleOutcome.NO_MATCH,
|
|
127
152
|
files=(
|
|
128
153
|
ExampleFile.python(
|
|
129
154
|
"tests/test_delivery.py",
|
|
130
|
-
"from unittest.mock import AsyncMock\n\nasync def test_delivery():\n send = AsyncMock()\n await deliver(send)\n send.
|
|
155
|
+
"from unittest.mock import AsyncMock\n\nasync def test_delivery(expected):\n send = AsyncMock()\n await deliver(send)\n send.assert_called_once()\n send.assert_has_awaits(expected)\n",
|
|
131
156
|
),
|
|
132
157
|
),
|
|
133
158
|
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
@@ -166,7 +191,7 @@ class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
|
166
191
|
line=call.lineno,
|
|
167
192
|
col=call.col_offset + 1,
|
|
168
193
|
code=self.code,
|
|
169
|
-
severity=Severity.
|
|
194
|
+
severity=Severity.WARNING,
|
|
170
195
|
message="AsyncMock call assertions do not prove awaiting; keep this assertion and add an await assertion",
|
|
171
196
|
)
|
|
172
197
|
for test in _collected_tests(tree.body)
|
|
@@ -231,7 +256,7 @@ def _call_oracles(statement: ast.stmt) -> Iterator[tuple[_Reference, ast.Call |
|
|
|
231
256
|
):
|
|
232
257
|
yield _reference(receiver), call
|
|
233
258
|
case ast.Assert(test=condition):
|
|
234
|
-
for reference in sorted(
|
|
259
|
+
for reference in sorted(_asserted_states(condition, _CALL_STATE)):
|
|
235
260
|
oracle = next(
|
|
236
261
|
node
|
|
237
262
|
for node in walk_ast(condition)
|
|
@@ -304,49 +329,60 @@ def _await_oracles(statements: list[ast.stmt]) -> set[_Reference]:
|
|
|
304
329
|
case ast.Expr(value=ast.Call(func=ast.Attribute(value=receiver, attr=method)) as call) if (
|
|
305
330
|
method in _AWAIT_ASSERTIONS
|
|
306
331
|
):
|
|
307
|
-
if method != "assert_has_awaits" or
|
|
332
|
+
if method != "assert_has_awaits" or _calls_evidence(call) is not _Evidence.INSUFFICIENT:
|
|
308
333
|
covered.add(_reference(receiver))
|
|
309
334
|
case ast.Assert(test=condition):
|
|
310
|
-
covered.update(
|
|
335
|
+
covered.update(_asserted_states(condition, _AWAIT_STATE))
|
|
311
336
|
case _:
|
|
312
337
|
pass
|
|
313
338
|
return covered
|
|
314
339
|
|
|
315
340
|
|
|
316
|
-
def
|
|
341
|
+
def _calls_evidence(call: ast.Call) -> _Evidence:
|
|
317
342
|
if call.args:
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
343
|
+
return _sequence_evidence(call.args[0])
|
|
344
|
+
for keyword in call.keywords:
|
|
345
|
+
if keyword.arg == "calls":
|
|
346
|
+
return _sequence_evidence(keyword.value)
|
|
347
|
+
return _Evidence.UNRESOLVED
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _sequence_evidence(node: ast.expr) -> _Evidence:
|
|
351
|
+
if isinstance(node, (ast.List, ast.Tuple)):
|
|
352
|
+
if not node.elts:
|
|
353
|
+
return _Evidence.INSUFFICIENT
|
|
354
|
+
if any(not isinstance(item, ast.Starred) for item in node.elts):
|
|
355
|
+
return _Evidence.POSITIVE
|
|
356
|
+
return _Evidence.UNRESOLVED
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _asserted_states(condition: ast.expr, states: frozenset[str]) -> set[_Reference]:
|
|
360
|
+
match condition:
|
|
361
|
+
case ast.UnaryOp(op=ast.Not(), operand=ast.UnaryOp(op=ast.Not(), operand=operand)):
|
|
362
|
+
return _asserted_states(operand, states)
|
|
363
|
+
case ast.UnaryOp(op=ast.Not(), operand=ast.Compare(left=left, ops=[operator], comparators=[right])):
|
|
364
|
+
inverse = _negated_operator(operator)
|
|
365
|
+
return _comparison_states(left, inverse, right, states) if inverse is not None else set()
|
|
366
|
+
case ast.UnaryOp(op=ast.Not(), operand=ast.Attribute(attr=attribute)) if attribute in states:
|
|
367
|
+
return set()
|
|
368
|
+
case ast.BoolOp(op=operator, values=values):
|
|
369
|
+
branches = [_asserted_states(value, states) for value in values]
|
|
370
|
+
return (
|
|
371
|
+
branches[0].union(*branches[1:])
|
|
372
|
+
if isinstance(operator, ast.And)
|
|
373
|
+
else branches[0].intersection(*branches[1:])
|
|
374
|
+
)
|
|
375
|
+
case ast.Attribute(value=receiver, attr=attribute) if attribute in states:
|
|
376
|
+
return {_reference(receiver)}
|
|
377
|
+
case ast.Compare(left=left, ops=operators, comparators=rights):
|
|
378
|
+
result: set[_Reference] = set()
|
|
379
|
+
for first, operator, second in zip((left, *rights[:-1]), operators, rights, strict=True):
|
|
380
|
+
result.update(_comparison_states(first, operator, second, states))
|
|
381
|
+
return result
|
|
382
|
+
case _:
|
|
383
|
+
# Preserve acceptance of unresolved await-state assertions, such as
|
|
384
|
+
# len(mock.await_args_list) == expected, without value propagation.
|
|
385
|
+
return _unresolved_await_states(condition, states)
|
|
350
386
|
|
|
351
387
|
|
|
352
388
|
def _negated_operator(operator: ast.cmpop) -> ast.cmpop | None:
|
|
@@ -364,6 +400,18 @@ def _negated_operator(operator: ast.cmpop) -> ast.cmpop | None:
|
|
|
364
400
|
return factory() if factory is not None else None
|
|
365
401
|
|
|
366
402
|
|
|
403
|
+
def _comparison_states(left: ast.expr, operator: ast.cmpop, right: ast.expr, states: frozenset[str]) -> set[_Reference]:
|
|
404
|
+
result: set[_Reference] = set()
|
|
405
|
+
for node, comparison, expected in ((left, operator, right), (right, _reversed_operator(operator), left)):
|
|
406
|
+
if isinstance(node, ast.Attribute) and node.attr in states:
|
|
407
|
+
evidence = _state_evidence(node.attr, comparison, expected)
|
|
408
|
+
if evidence is _Evidence.POSITIVE or (states == _AWAIT_STATE and evidence is _Evidence.UNRESOLVED):
|
|
409
|
+
result.add(_reference(node.value))
|
|
410
|
+
else:
|
|
411
|
+
result.update(_unresolved_await_states(node, states))
|
|
412
|
+
return result
|
|
413
|
+
|
|
414
|
+
|
|
367
415
|
def _reversed_operator(operator: ast.cmpop) -> ast.cmpop:
|
|
368
416
|
match operator:
|
|
369
417
|
case ast.Lt():
|
|
@@ -378,32 +426,38 @@ def _reversed_operator(operator: ast.cmpop) -> ast.cmpop:
|
|
|
378
426
|
return operator
|
|
379
427
|
|
|
380
428
|
|
|
381
|
-
def
|
|
382
|
-
if
|
|
429
|
+
def _unresolved_await_states(node: ast.expr, states: frozenset[str]) -> set[_Reference]:
|
|
430
|
+
if states != _AWAIT_STATE:
|
|
383
431
|
return set()
|
|
432
|
+
return {
|
|
433
|
+
_reference(child.value) for child in walk_ast(node) if isinstance(child, ast.Attribute) and child.attr in states
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _state_evidence(attribute: str, operator: ast.cmpop, expected: ast.expr) -> _Evidence:
|
|
438
|
+
positive: bool
|
|
384
439
|
if (
|
|
385
|
-
|
|
386
|
-
and isinstance(
|
|
387
|
-
and
|
|
388
|
-
and left.attr in {"args", "kwargs"}
|
|
440
|
+
attribute in {"call_count", "await_count"}
|
|
441
|
+
and isinstance(expected, ast.Constant)
|
|
442
|
+
and isinstance(expected.value, int)
|
|
389
443
|
):
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if left.attr in {"call_count", "await_count"} and isinstance(right, ast.Constant) and type(right.value) is int:
|
|
395
|
-
positive = _proves_positive_count(operator, right.value)
|
|
396
|
-
elif left.attr == "called" and isinstance(right, ast.Constant):
|
|
397
|
-
positive = (isinstance(operator, (ast.Eq, ast.Is)) and right.value is True) or (
|
|
398
|
-
isinstance(operator, (ast.NotEq, ast.IsNot)) and right.value is False
|
|
444
|
+
positive = _proves_positive_count(operator, expected.value)
|
|
445
|
+
elif attribute == "called" and isinstance(expected, ast.Constant) and isinstance(expected.value, bool):
|
|
446
|
+
positive = (isinstance(operator, (ast.Eq, ast.Is)) and expected.value) or (
|
|
447
|
+
isinstance(operator, (ast.NotEq, ast.IsNot)) and not expected.value
|
|
399
448
|
)
|
|
400
|
-
elif
|
|
449
|
+
elif attribute == "await_args" and isinstance(expected, ast.Constant) and expected.value is None:
|
|
401
450
|
positive = isinstance(operator, (ast.NotEq, ast.IsNot))
|
|
402
|
-
elif
|
|
403
|
-
|
|
404
|
-
|
|
451
|
+
elif attribute == "await_args_list" and isinstance(operator, (ast.Eq, ast.NotEq)):
|
|
452
|
+
sequence = _sequence_evidence(expected)
|
|
453
|
+
if sequence is _Evidence.UNRESOLVED:
|
|
454
|
+
return sequence
|
|
455
|
+
positive = (isinstance(operator, ast.Eq) and sequence is _Evidence.POSITIVE) or (
|
|
456
|
+
isinstance(operator, ast.NotEq) and isinstance(expected, ast.List) and sequence is _Evidence.INSUFFICIENT
|
|
405
457
|
)
|
|
406
|
-
|
|
458
|
+
else:
|
|
459
|
+
return _Evidence.UNRESOLVED
|
|
460
|
+
return _Evidence.POSITIVE if positive else _Evidence.INSUFFICIENT
|
|
407
461
|
|
|
408
462
|
|
|
409
463
|
def _proves_positive_count(operator: ast.cmpop, value: int) -> bool:
|
|
@@ -418,10 +472,6 @@ def _proves_positive_count(operator: ast.cmpop, value: int) -> bool:
|
|
|
418
472
|
return False
|
|
419
473
|
|
|
420
474
|
|
|
421
|
-
def _nonempty_sequence(node: ast.expr) -> bool:
|
|
422
|
-
return isinstance(node, (ast.List, ast.Tuple)) and any(not isinstance(item, ast.Starred) for item in node.elts)
|
|
423
|
-
|
|
424
|
-
|
|
425
475
|
def _invalidates_reference(node: ast.AST, reference: _Reference, binding: ast.Name) -> bool:
|
|
426
476
|
match node:
|
|
427
477
|
case ast.Name(id=name, ctx=ast.Store() | ast.Del()):
|
|
@@ -53,50 +53,23 @@ class PreferMonkeypatchForProcessStateInTest(Rule):
|
|
|
53
53
|
"mutation can survive an assertion or setup failure, making later tests depend on execution order."
|
|
54
54
|
),
|
|
55
55
|
remediation=(
|
|
56
|
-
"
|
|
57
|
-
"monkeypatch.delitem so
|
|
58
|
-
"
|
|
56
|
+
"Establish restoration in setup with pytest's monkeypatch.chdir, monkeypatch.syspath_prepend, "
|
|
57
|
+
"monkeypatch.setitem, or monkeypatch.delitem so the original state is recorded before the mutation. "
|
|
58
|
+
"Alternatively, explicitly restore the original state with a matching try/finally. Adding monkeypatch "
|
|
59
|
+
"operations only during teardown can undo intended cleanup: undoing a teardown deletion can reinstall "
|
|
60
|
+
"a temporary sys.modules entry. Intentional cleanup with established ownership may use a local, "
|
|
61
|
+
"reasoned SARJ446 suppression."
|
|
59
62
|
),
|
|
60
63
|
category=RuleCategory.TESTING,
|
|
61
64
|
autofix=AutofixPolicy.NONE,
|
|
62
65
|
limitations=(
|
|
63
|
-
"Teardown
|
|
66
|
+
"Teardown ownership and original-state restoration across yield are not inferred. Valid cleanup or restoration may still warn and require a reasoned local suppression.",
|
|
64
67
|
"Only maintained test paths are analyzed; generated files, production code, and module/class bootstrap mutations are excluded.",
|
|
65
68
|
"The rule covers os.chdir, sys.path.insert(0, ...), and direct sys.modules set, delete, or unused pop operations with an equivalent pytest restoring helper.",
|
|
66
69
|
"Environment variables remain owned by Ruff TID251/B003; argv, locale, timezone, warning filters, and process APIs without an equivalent helper are outside this rule.",
|
|
67
70
|
"Imports and import aliases are resolved conservatively. Indirect container aliases and dynamically selected modules are not inferred.",
|
|
68
71
|
),
|
|
69
72
|
examples=(
|
|
70
|
-
RuleExample(
|
|
71
|
-
example_id="fixture-cleanup-unscoped",
|
|
72
|
-
title="Restore setup mutations even when setup fails",
|
|
73
|
-
outcome=ExampleOutcome.MATCH,
|
|
74
|
-
files=(
|
|
75
|
-
ExampleFile.python(
|
|
76
|
-
"tests/test_plugins.py",
|
|
77
|
-
"import sys\nimport pytest\n@pytest.fixture\ndef fresh_plugin():\n sys.modules.pop('temporary_plugin', None)\n yield\n",
|
|
78
|
-
),
|
|
79
|
-
),
|
|
80
|
-
focus_path=PurePosixPath("tests/test_plugins.py"),
|
|
81
|
-
expected_count=1,
|
|
82
|
-
public=True,
|
|
83
|
-
scenario="fixture-cleanup",
|
|
84
|
-
),
|
|
85
|
-
RuleExample(
|
|
86
|
-
example_id="yield-fixture-cleanup",
|
|
87
|
-
title="Do not undo fixture teardown",
|
|
88
|
-
outcome=ExampleOutcome.NO_MATCH,
|
|
89
|
-
files=(
|
|
90
|
-
ExampleFile.python(
|
|
91
|
-
"tests/test_plugins.py",
|
|
92
|
-
"import sys\nimport pytest\n@pytest.fixture\ndef fresh_plugin():\n yield\n sys.modules.pop('temporary_plugin', None)\n",
|
|
93
|
-
),
|
|
94
|
-
),
|
|
95
|
-
focus_path=PurePosixPath("tests/test_plugins.py"),
|
|
96
|
-
expected_count=0,
|
|
97
|
-
public=True,
|
|
98
|
-
scenario="fixture-cleanup",
|
|
99
|
-
),
|
|
100
73
|
RuleExample(
|
|
101
74
|
example_id="direct-module-registry-mutation",
|
|
102
75
|
title="Test installs a module without guaranteed restoration",
|
|
@@ -143,7 +116,7 @@ class PreferMonkeypatchForProcessStateInTest(Rule):
|
|
|
143
116
|
mutations = [
|
|
144
117
|
mutation
|
|
145
118
|
for function in _functions(tree, node_index=context.node_index)
|
|
146
|
-
for mutation in _function_mutations(function, module_imports
|
|
119
|
+
for mutation in _function_mutations(function, module_imports)
|
|
147
120
|
]
|
|
148
121
|
diagnostics = [
|
|
149
122
|
Diagnostic(
|
|
@@ -153,7 +126,8 @@ class PreferMonkeypatchForProcessStateInTest(Rule):
|
|
|
153
126
|
code=self.code,
|
|
154
127
|
message=(
|
|
155
128
|
f"Direct {mutation.subject} mutation can leak into later tests when this scope exits early. "
|
|
156
|
-
f"
|
|
129
|
+
f"Establish restoration before the setup mutation using {mutation.remedy}, or explicitly restore "
|
|
130
|
+
"the original state. Teardown-only monkeypatch use can undo intended cleanup."
|
|
157
131
|
),
|
|
158
132
|
severity=Severity.WARNING,
|
|
159
133
|
)
|
|
@@ -174,8 +148,6 @@ def _functions(
|
|
|
174
148
|
def _function_mutations(
|
|
175
149
|
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
176
150
|
module_imports: ImportIndex,
|
|
177
|
-
*,
|
|
178
|
-
module_level: bool,
|
|
179
151
|
) -> list[_Mutation]:
|
|
180
152
|
local_tree = ast.Module(body=function.body, type_ignores=[])
|
|
181
153
|
local_imports = ImportIndex.from_tree(local_tree)
|
|
@@ -188,33 +160,9 @@ def _function_mutations(
|
|
|
188
160
|
continue
|
|
189
161
|
mutations.append(mutation)
|
|
190
162
|
restored = _restored_mutations(nodes, mutations, module_imports, local_imports)
|
|
191
|
-
if module_level:
|
|
192
|
-
restored.update(_fixture_teardown_nodes(function, nodes, module_imports))
|
|
193
163
|
return [mutation for mutation in mutations if mutation.node not in restored]
|
|
194
164
|
|
|
195
165
|
|
|
196
|
-
def _fixture_teardown_nodes(
|
|
197
|
-
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
198
|
-
nodes: tuple[ast.AST, ...],
|
|
199
|
-
module_imports: ImportIndex,
|
|
200
|
-
) -> set[ast.AST]:
|
|
201
|
-
if not any(
|
|
202
|
-
module_imports.resolves(
|
|
203
|
-
decorator.func if isinstance(decorator, ast.Call) else decorator,
|
|
204
|
-
sources=frozenset({"pytest", "pytest_asyncio"}),
|
|
205
|
-
symbol="fixture",
|
|
206
|
-
)
|
|
207
|
-
for decorator in function.decorator_list
|
|
208
|
-
):
|
|
209
|
-
return set()
|
|
210
|
-
if sum(isinstance(node, ast.Yield | ast.YieldFrom) for node in nodes) != 1:
|
|
211
|
-
return set()
|
|
212
|
-
for index, statement in enumerate(function.body):
|
|
213
|
-
if isinstance(statement, ast.Expr) and isinstance(statement.value, ast.Yield):
|
|
214
|
-
return set(_descendants(function.body[index + 1 :]))
|
|
215
|
-
return set()
|
|
216
|
-
|
|
217
|
-
|
|
218
166
|
def _lexical_nodes(function: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[ast.AST]:
|
|
219
167
|
stack: list[ast.AST] = [node for node in reversed(function.body) if not isinstance(node, _SCOPE_NODES)]
|
|
220
168
|
while stack:
|
|
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.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_contract_annotation.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_resource_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/excessive_commentary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_any_mapping_types.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_delete_statement.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_fastapi_on_event.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.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_offset_pagination.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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_restated_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/phase_label_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/prefer_library_fake.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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/redundant_docstring.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
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/typed_error_reasons.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.0 → sarj_python_lint-1.8.2}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|