sarj-python-lint 1.27.3__tar.gz → 1.27.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/PKG-INFO +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/pyproject.toml +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_file_context.py +5 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_source.py +6 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/interpreter_argv.py +101 -8
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_project_index.py +6 -22
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +33 -3
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_dunder_all.py +26 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +29 -9
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +33 -3
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +31 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +31 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +6 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_native_string_check.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +32 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +36 -3
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +36 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_str_enum.py +64 -12
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/production_derived_test_cases.py +3 -3
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +4 -4
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_port_for_service.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +32 -7
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +1 -1
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/typed_error_reasons.py +32 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/unused_test_factory_option.py +2 -2
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/.gitignore +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/LICENSE +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/README.md +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.27.
|
|
3
|
+
Version: 1.27.4
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -5,6 +5,7 @@ from functools import cached_property
|
|
|
5
5
|
from typing import TYPE_CHECKING, final
|
|
6
6
|
|
|
7
7
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
8
|
+
from sarj_python_lint._source import symbol_prefilter_source
|
|
8
9
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
9
10
|
from sarj_python_lint.rules._fastapi import FastapiIndex
|
|
10
11
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
@@ -26,6 +27,10 @@ class PythonFileContext:
|
|
|
26
27
|
self.source = source
|
|
27
28
|
self.session = session if session is not None else AnalysisSession()
|
|
28
29
|
|
|
30
|
+
@cached_property
|
|
31
|
+
def symbol_source(self) -> str:
|
|
32
|
+
return symbol_prefilter_source(self.source)
|
|
33
|
+
|
|
29
34
|
@cached_property
|
|
30
35
|
def tree(self) -> ast.Module | None:
|
|
31
36
|
if self.session.project is not None:
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from io import BytesIO
|
|
4
4
|
import tokenize
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
|
+
import unicodedata
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
@@ -16,3 +17,8 @@ def python_source_encoding(data: bytes) -> str:
|
|
|
16
17
|
def read_python_source(path: Path) -> str:
|
|
17
18
|
with tokenize.open(path) as stream:
|
|
18
19
|
return stream.read()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def symbol_prefilter_source(source: str) -> str:
|
|
23
|
+
# Only necessary symbol gates may use this view; parse and locate original source.
|
|
24
|
+
return source if source.isascii() else unicodedata.normalize("NFKC", source)
|
{sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
@@ -28,9 +28,45 @@ class InterpreterInvocation:
|
|
|
28
28
|
class ParsedOption:
|
|
29
29
|
invocation: InterpreterInvocation | None = None
|
|
30
30
|
consumed: int = 1
|
|
31
|
+
stdin_mode: bool = False
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
_SHELLS = frozenset({"sh", "bash", "dash", "zsh", "ksh"})
|
|
35
|
+
_SHELL_SHORT_FLAGS = frozenset("abefhkmnptuvxBCEHPrDilscoO")
|
|
36
|
+
_SHELL_LONG_FLAGS = frozenset(
|
|
37
|
+
{"--norc", "--noprofile", "--posix", "--restricted", "--verbose", "--login", "--noediting"}
|
|
38
|
+
)
|
|
39
|
+
_SHELL_NAMED_OPTIONS = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"allexport",
|
|
42
|
+
"braceexpand",
|
|
43
|
+
"emacs",
|
|
44
|
+
"errexit",
|
|
45
|
+
"errtrace",
|
|
46
|
+
"functrace",
|
|
47
|
+
"hashall",
|
|
48
|
+
"histexpand",
|
|
49
|
+
"history",
|
|
50
|
+
"ignoreeof",
|
|
51
|
+
"interactive-comments",
|
|
52
|
+
"keyword",
|
|
53
|
+
"monitor",
|
|
54
|
+
"noclobber",
|
|
55
|
+
"noexec",
|
|
56
|
+
"noglob",
|
|
57
|
+
"nolog",
|
|
58
|
+
"notify",
|
|
59
|
+
"nounset",
|
|
60
|
+
"onecmd",
|
|
61
|
+
"physical",
|
|
62
|
+
"pipefail",
|
|
63
|
+
"posix",
|
|
64
|
+
"privileged",
|
|
65
|
+
"verbose",
|
|
66
|
+
"vi",
|
|
67
|
+
"xtrace",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
34
70
|
_NODE_VALUE_FLAGS = frozenset(
|
|
35
71
|
{
|
|
36
72
|
"--require",
|
|
@@ -200,7 +236,9 @@ def classify_interpreter(argv: Sequence[str]) -> InterpreterInvocation:
|
|
|
200
236
|
if is_python_executable(executable):
|
|
201
237
|
return _python(arguments[1:])
|
|
202
238
|
if executable in _SHELLS:
|
|
203
|
-
return _short_options(
|
|
239
|
+
return _short_options(
|
|
240
|
+
arguments[1:], source="c", values=frozenset({"o", "O"}), shell_mode=True, bash_mode=executable == "bash"
|
|
241
|
+
)
|
|
204
242
|
if executable in {"node", "nodejs"}:
|
|
205
243
|
return _node(arguments[1:])
|
|
206
244
|
if executable == "jq":
|
|
@@ -304,31 +342,66 @@ def _ruby_long_option(argument: str, following: str | None) -> ParsedOption:
|
|
|
304
342
|
|
|
305
343
|
|
|
306
344
|
def _short_options(
|
|
307
|
-
arguments: Sequence[str], *, source: str, values: frozenset[str], shell_mode: bool = False
|
|
345
|
+
arguments: Sequence[str], *, source: str, values: frozenset[str], shell_mode: bool = False, bash_mode: bool = False
|
|
308
346
|
) -> InterpreterInvocation:
|
|
309
347
|
index = 0
|
|
348
|
+
stdin_mode = False
|
|
349
|
+
short_seen = False
|
|
310
350
|
while index < len(arguments):
|
|
311
351
|
boundary = _external_boundary(arguments, index, plus_options=True)
|
|
312
352
|
if boundary is not None:
|
|
313
|
-
return boundary
|
|
353
|
+
return InterpreterInvocation("stdin") if stdin_mode else boundary
|
|
314
354
|
argument = arguments[index]
|
|
315
355
|
if argument.startswith("--"):
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
356
|
+
parsed = _long_option(arguments, index, shell_mode=shell_mode, bash_mode=bash_mode, short_seen=short_seen)
|
|
357
|
+
else:
|
|
358
|
+
short_seen = True
|
|
359
|
+
parsed = _short_option(
|
|
360
|
+
arguments, index, source=source, values=values, shell_mode=shell_mode, bash_mode=bash_mode
|
|
361
|
+
)
|
|
319
362
|
if parsed.invocation is not None:
|
|
320
363
|
return parsed.invocation
|
|
364
|
+
stdin_mode = stdin_mode or parsed.stdin_mode
|
|
321
365
|
index += parsed.consumed
|
|
322
366
|
return InterpreterInvocation("stdin")
|
|
323
367
|
|
|
324
368
|
|
|
369
|
+
def _long_option(
|
|
370
|
+
arguments: Sequence[str], index: int, *, shell_mode: bool, bash_mode: bool, short_seen: bool
|
|
371
|
+
) -> ParsedOption:
|
|
372
|
+
if not bash_mode:
|
|
373
|
+
consumed = 2 if shell_mode and arguments[index] in {"--rcfile", "--init-file"} else 1
|
|
374
|
+
return ParsedOption(consumed=consumed)
|
|
375
|
+
if short_seen:
|
|
376
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
377
|
+
argument = arguments[index]
|
|
378
|
+
if argument in {"--help", "--version"}:
|
|
379
|
+
return ParsedOption(InterpreterInvocation("other"))
|
|
380
|
+
if argument in {"--rcfile", "--init-file"}:
|
|
381
|
+
return (
|
|
382
|
+
ParsedOption(consumed=2)
|
|
383
|
+
if _following_operand(arguments, index) is not None
|
|
384
|
+
else ParsedOption(InterpreterInvocation("unknown"))
|
|
385
|
+
)
|
|
386
|
+
if argument not in _SHELL_LONG_FLAGS:
|
|
387
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
388
|
+
return ParsedOption()
|
|
389
|
+
|
|
390
|
+
|
|
325
391
|
def _short_option(
|
|
326
|
-
arguments: Sequence[str],
|
|
392
|
+
arguments: Sequence[str],
|
|
393
|
+
index: int,
|
|
394
|
+
*,
|
|
395
|
+
source: str,
|
|
396
|
+
values: frozenset[str],
|
|
397
|
+
shell_mode: bool,
|
|
398
|
+
bash_mode: bool = False,
|
|
327
399
|
) -> ParsedOption:
|
|
400
|
+
if bash_mode:
|
|
401
|
+
return _shell_short_option(arguments, index, values)
|
|
328
402
|
argument = arguments[index]
|
|
329
403
|
for cursor, flag in enumerate(argument[1:], 1):
|
|
330
404
|
if flag in source:
|
|
331
|
-
# Shell -c always consumes the next word, including inside -ceu.
|
|
332
405
|
attached = "" if shell_mode else argument[cursor + 1 :]
|
|
333
406
|
operand = attached or _following_operand(arguments, index)
|
|
334
407
|
forwarded = tuple(arguments[index + 3 :]) if shell_mode else ()
|
|
@@ -338,6 +411,26 @@ def _short_option(
|
|
|
338
411
|
return ParsedOption()
|
|
339
412
|
|
|
340
413
|
|
|
414
|
+
def _shell_short_option(arguments: Sequence[str], index: int, values: frozenset[str]) -> ParsedOption:
|
|
415
|
+
flags = arguments[index][1:]
|
|
416
|
+
if any(flag not in _SHELL_SHORT_FLAGS for flag in flags):
|
|
417
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
418
|
+
consumed = 1
|
|
419
|
+
for flag in flags:
|
|
420
|
+
if flag in values:
|
|
421
|
+
operand = _following_operand(arguments, index + consumed - 1)
|
|
422
|
+
# Bash shopt (-O) has a separate, version-dependent namespace. Keep
|
|
423
|
+
# it unproven until a native option contract is available.
|
|
424
|
+
if flag == "O" or operand not in _SHELL_NAMED_OPTIONS:
|
|
425
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
426
|
+
consumed += 1
|
|
427
|
+
if "c" in flags:
|
|
428
|
+
operand = _following_operand(arguments, index + consumed - 1)
|
|
429
|
+
forwarded = tuple(arguments[index + consumed + 2 :])
|
|
430
|
+
return ParsedOption(InterpreterInvocation("shell", operand, forwarded))
|
|
431
|
+
return ParsedOption(consumed=consumed, stdin_mode="s" in flags)
|
|
432
|
+
|
|
433
|
+
|
|
341
434
|
def _node(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
342
435
|
index = 0
|
|
343
436
|
while index < len(arguments):
|
{sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
@@ -11,7 +11,7 @@ import re
|
|
|
11
11
|
from types import MappingProxyType
|
|
12
12
|
from typing import TYPE_CHECKING, final
|
|
13
13
|
|
|
14
|
-
from sarj_python_lint._source import read_python_source
|
|
14
|
+
from sarj_python_lint._source import read_python_source, symbol_prefilter_source
|
|
15
15
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
16
16
|
from sarj_python_lint.rules._first_party import FirstPartyFacts, project_root
|
|
17
17
|
from sarj_python_lint.rules._paths import is_generated
|
|
@@ -46,7 +46,6 @@ _MAX_FILES_PER_ROOT = 10_000
|
|
|
46
46
|
_MAX_FILE_BYTES = 500_000
|
|
47
47
|
_MAX_SOURCE_CHARS_PER_ROOT = 50_000_000
|
|
48
48
|
_NEW_TYPE_MIN_ARGS = 2
|
|
49
|
-
_MATCH_CLASS_RE: re.Pattern[str] = re.compile(r"\bcase\s+([A-Z][A-Za-z0-9_]*)\s*\(")
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
@dataclass(frozen=True, slots=True)
|
|
@@ -210,7 +209,7 @@ class ProjectIndexSet:
|
|
|
210
209
|
return frozenset(
|
|
211
210
|
(candidate.path, node.name)
|
|
212
211
|
for candidate in self._units.values()
|
|
213
|
-
if candidate.tree is not None
|
|
212
|
+
if candidate.tree is not None
|
|
214
213
|
for node in candidate.tree.body
|
|
215
214
|
if isinstance(node, ast.ClassDef)
|
|
216
215
|
and any(_direct_base_symbol(candidate, base) == target for base in node.bases)
|
|
@@ -223,7 +222,7 @@ class ProjectIndexSet:
|
|
|
223
222
|
return frozenset(
|
|
224
223
|
(candidate.path, owner.name)
|
|
225
224
|
for candidate in self._units.values()
|
|
226
|
-
if candidate.tree is not None
|
|
225
|
+
if candidate.tree is not None
|
|
227
226
|
for owner in candidate.tree.body
|
|
228
227
|
if isinstance(owner, ast.ClassDef) and _class_has_typed_dependency(candidate, owner, target)
|
|
229
228
|
)
|
|
@@ -236,7 +235,6 @@ class ProjectIndexSet:
|
|
|
236
235
|
candidate.path
|
|
237
236
|
for candidate in self._units.values()
|
|
238
237
|
if candidate.tree is not None
|
|
239
|
-
and name in candidate.source
|
|
240
238
|
and any(
|
|
241
239
|
_is_mock_spec_for(candidate, call, target)
|
|
242
240
|
for call in walk_ast(candidate.tree)
|
|
@@ -265,15 +263,9 @@ class ProjectIndexSet:
|
|
|
265
263
|
|
|
266
264
|
|
|
267
265
|
def _units(sources: Mapping[Path, str], roots: Sequence[Path] = ()) -> dict[Path, SourceUnit]:
|
|
268
|
-
matched_classes = {
|
|
269
|
-
match.group(1)
|
|
270
|
-
for source in sources.values()
|
|
271
|
-
if "match " in source and "str(" in source
|
|
272
|
-
for match in _MATCH_CLASS_RE.finditer(source)
|
|
273
|
-
}
|
|
274
266
|
parsed: dict[Path, tuple[str | None, str, ast.Module | None]] = {}
|
|
275
267
|
for path, source in sources.items():
|
|
276
|
-
if not _is_index_candidate(source
|
|
268
|
+
if not _is_index_candidate(symbol_prefilter_source(source)):
|
|
277
269
|
continue
|
|
278
270
|
tree: ast.Module | None = None
|
|
279
271
|
with suppress(SyntaxError):
|
|
@@ -291,16 +283,8 @@ def _units(sources: Mapping[Path, str], roots: Sequence[Path] = ()) -> dict[Path
|
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
|
|
294
|
-
def _is_index_candidate(source: str
|
|
295
|
-
return (
|
|
296
|
-
"NewType(" in source
|
|
297
|
-
or "class " in source
|
|
298
|
-
or "spec=" in source
|
|
299
|
-
or "spec_set=" in source
|
|
300
|
-
or "create_autospec(" in source
|
|
301
|
-
or ("match " in source and "str(" in source)
|
|
302
|
-
or any(f"class {name}" in source for name in matched_classes)
|
|
303
|
-
)
|
|
286
|
+
def _is_index_candidate(source: str) -> bool:
|
|
287
|
+
return "NewType" in source or "class" in source or "spec" in source or ("match" in source and "str" in source)
|
|
304
288
|
|
|
305
289
|
|
|
306
290
|
def _module_name(path: Path, roots: Sequence[Path]) -> str | None:
|
|
@@ -68,7 +68,7 @@ class AsyncCleanupRegisteredSynchronously(Rule):
|
|
|
68
68
|
|
|
69
69
|
@override
|
|
70
70
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
71
|
-
if "callback" not in context.
|
|
71
|
+
if "callback" not in context.symbol_source or context.generated or context.tree is None:
|
|
72
72
|
return []
|
|
73
73
|
provenance = ResourceProvenance(context)
|
|
74
74
|
findings: list[Diagnostic] = []
|
|
@@ -101,6 +101,36 @@ class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
|
101
101
|
"Custom assertion helpers and intentional scheduling contracts may require a reasoned local suppression.",
|
|
102
102
|
),
|
|
103
103
|
examples=(
|
|
104
|
+
RuleExample(
|
|
105
|
+
example_id="spaced-positive-called-state",
|
|
106
|
+
scenario="called-attribute-spacing",
|
|
107
|
+
title="A spaced positive call state still needs await evidence",
|
|
108
|
+
outcome=ExampleOutcome.MATCH,
|
|
109
|
+
files=(
|
|
110
|
+
ExampleFile.python(
|
|
111
|
+
"tests/test_delivery.py",
|
|
112
|
+
"from unittest.mock import AsyncMock\nasync def test_delivery():\n send = AsyncMock()\n pending = send()\n pending.close()\n assert send. called\n",
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
116
|
+
expected_count=1,
|
|
117
|
+
public=True,
|
|
118
|
+
),
|
|
119
|
+
RuleExample(
|
|
120
|
+
example_id="spaced-called-with-await-evidence",
|
|
121
|
+
scenario="called-attribute-spacing",
|
|
122
|
+
title="Await evidence covers the same spaced call state",
|
|
123
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
124
|
+
files=(
|
|
125
|
+
ExampleFile.python(
|
|
126
|
+
"tests/test_delivery.py",
|
|
127
|
+
"from unittest.mock import AsyncMock\nasync def test_delivery():\n send = AsyncMock()\n await send()\n assert send. called\n send.assert_awaited_once()\n",
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
focus_path=PurePosixPath("tests/test_delivery.py"),
|
|
131
|
+
expected_count=0,
|
|
132
|
+
public=True,
|
|
133
|
+
),
|
|
104
134
|
RuleExample(
|
|
105
135
|
example_id="call-only-async-mock",
|
|
106
136
|
title="A call assertion does not prove awaiting",
|
|
@@ -166,11 +196,11 @@ class AsyncMockCallWithoutAwaitAssertion(Rule):
|
|
|
166
196
|
@override
|
|
167
197
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
168
198
|
path = context.path
|
|
169
|
-
|
|
199
|
+
signals = context.symbol_source
|
|
170
200
|
if (
|
|
171
201
|
path.suffix != ".py"
|
|
172
|
-
or "AsyncMock" not in
|
|
173
|
-
or not any(token in
|
|
202
|
+
or "AsyncMock" not in signals
|
|
203
|
+
or not any(token in signals for token in ("called", "call_count"))
|
|
174
204
|
or not (path.stem.startswith("test_") or path.stem.endswith("_test"))
|
|
175
205
|
or context.generated
|
|
176
206
|
):
|
{sarj_python_lint-1.27.3 → sarj_python_lint-1.27.4}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
@@ -85,6 +85,30 @@ class NoDunderAll(Rule):
|
|
|
85
85
|
expected_count=0,
|
|
86
86
|
public=True,
|
|
87
87
|
),
|
|
88
|
+
RuleExample(
|
|
89
|
+
example_id="normalized-module-export-binding",
|
|
90
|
+
title="Compatibility identifiers bind the native module export name",
|
|
91
|
+
outcome=ExampleOutcome.MATCH,
|
|
92
|
+
files=(ExampleFile.python("app/service.py", '__all__ = ["value"]\nvalue = 1\n'),),
|
|
93
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
94
|
+
expected_count=1,
|
|
95
|
+
public=True,
|
|
96
|
+
scenario="normalized-module-name",
|
|
97
|
+
),
|
|
98
|
+
RuleExample(
|
|
99
|
+
example_id="normalized-local-binding",
|
|
100
|
+
title="A local binding is outside the module export contract",
|
|
101
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
102
|
+
files=(
|
|
103
|
+
ExampleFile.python(
|
|
104
|
+
"app/service.py", 'def values():\n __all__ = ["value"]\n return __all__\n'
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
108
|
+
expected_count=0,
|
|
109
|
+
public=True,
|
|
110
|
+
scenario="normalized-module-name",
|
|
111
|
+
),
|
|
88
112
|
),
|
|
89
113
|
)
|
|
90
114
|
description = documentation.summary
|
|
@@ -92,8 +116,8 @@ class NoDunderAll(Rule):
|
|
|
92
116
|
@override
|
|
93
117
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
94
118
|
path = context.path
|
|
95
|
-
|
|
96
|
-
if path.suffix != ".py" or "__all__" not in
|
|
119
|
+
signals = context.symbol_source
|
|
120
|
+
if path.suffix != ".py" or "__all__" not in signals or context.generated:
|
|
97
121
|
return []
|
|
98
122
|
tree = context.tree
|
|
99
123
|
if tree is None:
|
|
@@ -135,8 +135,8 @@ class NoFixedSleepBeforeAssert(Rule):
|
|
|
135
135
|
@override
|
|
136
136
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
137
137
|
path = context.path
|
|
138
|
-
|
|
139
|
-
if not _is_collected_test_module(path) or "sleep" not in
|
|
138
|
+
signals = context.symbol_source
|
|
139
|
+
if not _is_collected_test_module(path) or "sleep" not in signals or context.generated:
|
|
140
140
|
return []
|
|
141
141
|
tree = context.tree
|
|
142
142
|
if tree is None:
|
|
@@ -139,7 +139,7 @@ class NoFixedSleepInLoop(Rule):
|
|
|
139
139
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
140
140
|
path = context.path
|
|
141
141
|
if (
|
|
142
|
-
"sleep" not in context.
|
|
142
|
+
"sleep" not in context.symbol_source
|
|
143
143
|
or is_test_path(path)
|
|
144
144
|
or is_test_support_path(path)
|
|
145
145
|
or context.generated
|
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
from pathlib import Path, PurePosixPath
|
|
8
8
|
from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
|
|
9
9
|
|
|
10
|
-
from sarj_python_lint._source import read_python_source
|
|
10
|
+
from sarj_python_lint._source import read_python_source, symbol_prefilter_source
|
|
11
11
|
from sarj_python_lint.rule_base import (
|
|
12
12
|
Diagnostic,
|
|
13
13
|
ExampleFile,
|
|
@@ -785,7 +785,7 @@ def _candidate_calls_class(
|
|
|
785
785
|
source = read_python_source(candidate)
|
|
786
786
|
except OSError, SyntaxError, UnicodeError:
|
|
787
787
|
return False
|
|
788
|
-
if class_name not in source or is_generated(candidate, source):
|
|
788
|
+
if class_name not in symbol_prefilter_source(source) or is_generated(candidate, source):
|
|
789
789
|
return False
|
|
790
790
|
try:
|
|
791
791
|
tree = ast.parse(source, filename=str(candidate))
|
|
@@ -117,7 +117,7 @@ class NoPositionalPsycopgRowEscape(Rule):
|
|
|
117
117
|
@override
|
|
118
118
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
119
119
|
path = context.path
|
|
120
|
-
|
|
120
|
+
signals = context.symbol_source
|
|
121
121
|
if (
|
|
122
122
|
context.generated
|
|
123
123
|
or is_test_path(path)
|
|
@@ -125,7 +125,7 @@ class NoPositionalPsycopgRowEscape(Rule):
|
|
|
125
125
|
or "migrations" in {part.lower() for part in path.parts}
|
|
126
126
|
):
|
|
127
127
|
return []
|
|
128
|
-
if "
|
|
128
|
+
if "cursor" not in signals or not any(method in signals for method in _FETCH_METHODS):
|
|
129
129
|
return []
|
|
130
130
|
tree = context.tree
|
|
131
131
|
if tree is None:
|
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from pathlib import PurePosixPath
|
|
5
|
-
import re
|
|
6
5
|
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
7
6
|
|
|
8
7
|
from sarj_python_lint.rule_base import (
|
|
@@ -24,7 +23,6 @@ if TYPE_CHECKING:
|
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
_SYS_SOURCES = frozenset({"sys"})
|
|
27
|
-
_PRIVATE_ALIAS_CANDIDATE_RE = re.compile(r"(?m)^(?![\d_])\w+(?:\s*:[^=\n]+)?(?:\s*=\s*(?!_)\w+)*\s*=\s*_(?!_)\w")
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
@final
|
|
@@ -126,6 +124,33 @@ class NoRedundantModuleAliasExports(Rule):
|
|
|
126
124
|
public=True,
|
|
127
125
|
scenario="module-replacement",
|
|
128
126
|
),
|
|
127
|
+
RuleExample(
|
|
128
|
+
example_id="native-parenthesized-alias-matching",
|
|
129
|
+
title="Native syntax preserves the existing ownership contract",
|
|
130
|
+
outcome=ExampleOutcome.MATCH,
|
|
131
|
+
files=(
|
|
132
|
+
ExampleFile.python(
|
|
133
|
+
"app/pagination.py",
|
|
134
|
+
"def _encode_cursor(value: str) -> str:\n return value\n\n\nencode_phone_number_cursor = (_encode_cursor)\n",
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
focus_path=PurePosixPath("app/pagination.py"),
|
|
138
|
+
expected_count=1,
|
|
139
|
+
public=True,
|
|
140
|
+
scenario="native-parenthesized-alias",
|
|
141
|
+
),
|
|
142
|
+
RuleExample(
|
|
143
|
+
example_id="native-parenthesized-alias-nonmatching",
|
|
144
|
+
title="Existing semantic ownership exclusions remain valid",
|
|
145
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
146
|
+
files=(
|
|
147
|
+
ExampleFile.python("app/pagination.py", '__version__ = "value"\npublic_version = (__version__)\n'),
|
|
148
|
+
),
|
|
149
|
+
focus_path=PurePosixPath("app/pagination.py"),
|
|
150
|
+
expected_count=0,
|
|
151
|
+
public=True,
|
|
152
|
+
scenario="native-parenthesized-alias",
|
|
153
|
+
),
|
|
129
154
|
),
|
|
130
155
|
)
|
|
131
156
|
description: str = documentation.summary
|
|
@@ -133,13 +158,8 @@ class NoRedundantModuleAliasExports(Rule):
|
|
|
133
158
|
@override
|
|
134
159
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
135
160
|
path = context.path
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
path.suffix != ".py"
|
|
140
|
-
or (not has_module_replacement_candidate and _PRIVATE_ALIAS_CANDIDATE_RE.search(source) is None)
|
|
141
|
-
or context.generated
|
|
142
|
-
):
|
|
161
|
+
signals = context.symbol_source
|
|
162
|
+
if path.suffix != ".py" or "=" not in context.source or "_" not in signals or context.generated:
|
|
143
163
|
return []
|
|
144
164
|
tree = context.tree
|
|
145
165
|
if tree is None:
|
|
@@ -25,7 +25,6 @@ if TYPE_CHECKING:
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
_DATA_NAME_RE = re.compile(r"(?:Settings|Config|Configuration|Options)$")
|
|
28
|
-
_DATA_CLASS_RE = re.compile(r"\bclass\s+\w+(?:Settings|Config|Configuration|Options)\b")
|
|
29
28
|
_COLLABORATOR_RE = re.compile(
|
|
30
29
|
r"(?:Client|Service|Store|Repository|Repo|Gateway|Provider|Pool|Publisher|Queue|Scheduler)$"
|
|
31
30
|
)
|
|
@@ -104,6 +103,36 @@ class NoServiceBehaviorInSettings(Rule):
|
|
|
104
103
|
expected_count=0,
|
|
105
104
|
public=True,
|
|
106
105
|
),
|
|
106
|
+
RuleExample(
|
|
107
|
+
example_id="bare-settings-matching",
|
|
108
|
+
title="Native syntax preserves the existing ownership contract",
|
|
109
|
+
outcome=ExampleOutcome.MATCH,
|
|
110
|
+
files=(
|
|
111
|
+
ExampleFile.python(
|
|
112
|
+
"app/settings.py",
|
|
113
|
+
"class Settings:\n def __init__(self, store: ScheduleStore) -> None:\n self._store = store\n\n async def repoint(self, batch_id: str) -> int:\n return await self._store.repoint(batch_id)\n",
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
focus_path=PurePosixPath("app/settings.py"),
|
|
117
|
+
expected_count=1,
|
|
118
|
+
public=True,
|
|
119
|
+
scenario="bare-settings",
|
|
120
|
+
),
|
|
121
|
+
RuleExample(
|
|
122
|
+
example_id="bare-settings-nonmatching",
|
|
123
|
+
title="Existing semantic ownership exclusions remain valid",
|
|
124
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
125
|
+
files=(
|
|
126
|
+
ExampleFile.python(
|
|
127
|
+
"app/settings.py",
|
|
128
|
+
"class Settings:\n def __init__(self, size: int): self.size = size\n def double(self): return self.size * 2\n",
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
focus_path=PurePosixPath("app/settings.py"),
|
|
132
|
+
expected_count=0,
|
|
133
|
+
public=True,
|
|
134
|
+
scenario="bare-settings",
|
|
135
|
+
),
|
|
107
136
|
),
|
|
108
137
|
)
|
|
109
138
|
description: str = documentation.summary
|
|
@@ -111,12 +140,13 @@ class NoServiceBehaviorInSettings(Rule):
|
|
|
111
140
|
@override
|
|
112
141
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
113
142
|
path = context.path
|
|
114
|
-
|
|
143
|
+
signals = context.symbol_source
|
|
115
144
|
if (
|
|
116
145
|
is_test_path(path)
|
|
117
146
|
or is_test_support_path(path)
|
|
118
147
|
or context.generated
|
|
119
|
-
or
|
|
148
|
+
or "class" not in signals
|
|
149
|
+
or not any(name in signals for name in ("Settings", "Config", "Options"))
|
|
120
150
|
):
|
|
121
151
|
return []
|
|
122
152
|
tree = context.tree
|
|
@@ -85,6 +85,36 @@ class NoStringConcatInLoop(Rule):
|
|
|
85
85
|
expected_count=0,
|
|
86
86
|
public=True,
|
|
87
87
|
),
|
|
88
|
+
RuleExample(
|
|
89
|
+
example_id="fstring-growth-in-loop",
|
|
90
|
+
title="F-string replacement repeatedly grows the accumulator",
|
|
91
|
+
outcome=ExampleOutcome.MATCH,
|
|
92
|
+
files=(
|
|
93
|
+
ExampleFile.python(
|
|
94
|
+
"app/render.py",
|
|
95
|
+
'def render(items):\n result = ""\n for item in items:\n result = f"{result}{item}"\n return result\n',
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
focus_path=PurePosixPath("app/render.py"),
|
|
99
|
+
expected_count=1,
|
|
100
|
+
public=True,
|
|
101
|
+
scenario="fstring-growth",
|
|
102
|
+
),
|
|
103
|
+
RuleExample(
|
|
104
|
+
example_id="joined-fstring-fragments",
|
|
105
|
+
title="Join rendered fragments after collecting them",
|
|
106
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
107
|
+
files=(
|
|
108
|
+
ExampleFile.python(
|
|
109
|
+
"app/render.py",
|
|
110
|
+
'def render(items):\n return "".join(f"{item}" for item in items)\n',
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
focus_path=PurePosixPath("app/render.py"),
|
|
114
|
+
expected_count=0,
|
|
115
|
+
public=True,
|
|
116
|
+
scenario="fstring-growth",
|
|
117
|
+
),
|
|
88
118
|
),
|
|
89
119
|
)
|
|
90
120
|
description = documentation.summary
|
|
@@ -95,7 +125,7 @@ class NoStringConcatInLoop(Rule):
|
|
|
95
125
|
source = context.source
|
|
96
126
|
if context.generated:
|
|
97
127
|
return []
|
|
98
|
-
if "+" not in source or ("for
|
|
128
|
+
if ("+" not in source and "{" not in source) or ("for" not in source and "while" not in source):
|
|
99
129
|
return []
|
|
100
130
|
tree = context.tree
|
|
101
131
|
if tree is None:
|
|
@@ -96,7 +96,7 @@ class NoSwallowedAsyncioCancellation(Rule):
|
|
|
96
96
|
|
|
97
97
|
@override
|
|
98
98
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
99
|
-
if "CancelledError" not in context.
|
|
99
|
+
if "CancelledError" not in context.symbol_source:
|
|
100
100
|
return []
|
|
101
101
|
if context.generated or is_test_path(context.path) or is_test_support_path(context.path):
|
|
102
102
|
return []
|
|
@@ -101,8 +101,8 @@ class NoUniqueViolationMessageMatch(Rule):
|
|
|
101
101
|
@override
|
|
102
102
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
103
103
|
path = context.path
|
|
104
|
-
|
|
105
|
-
if is_test_path(path) or context.generated or "UniqueViolation" not in
|
|
104
|
+
signals = context.symbol_source
|
|
105
|
+
if is_test_path(path) or context.generated or "UniqueViolation" not in signals:
|
|
106
106
|
return []
|
|
107
107
|
tree = context.tree
|
|
108
108
|
if tree is None:
|
|
@@ -129,7 +129,7 @@ class PreferAutospecForCallableMock(Rule):
|
|
|
129
129
|
|
|
130
130
|
@override
|
|
131
131
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
132
|
-
if not is_test_path(context.path) or context.generated or "Mock" not in context.
|
|
132
|
+
if not is_test_path(context.path) or context.generated or "Mock" not in context.symbol_source:
|
|
133
133
|
return []
|
|
134
134
|
tree = context.tree
|
|
135
135
|
if tree is None or _ambiguous_namespace(context):
|