sarj-python-lint 1.70.1__tar.gz → 1.72.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/__init__.py +2 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_file_context.py +2 -2
- sarj_python_lint-1.72.0/src/sarj_python_lint/_fixes.py +67 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_source.py +6 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rule_base.py +23 -0
- sarj_python_lint-1.72.0/src/sarj_python_lint/rules/_annotation_origins.py +74 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_annotation_semantics.py +2 -2
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_ast_index.py +26 -1
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_declared_contracts.py +38 -26
- sarj_python_lint-1.72.0/src/sarj_python_lint/rules/_local_alias_fix.py +161 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_test_composition.py +20 -14
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/excessive_commentary.py +13 -34
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +49 -7
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +16 -2
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +21 -12
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +38 -40
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +4 -21
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +16 -17
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +19 -13
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +18 -67
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/.gitignore +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/LICENSE +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/README.md +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/suppression_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.72.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from sarj_python_lint._analysis_session import AnalysisSession as AnalysisSession
|
|
2
|
+
from sarj_python_lint._fixes import apply_fixes as apply_fixes, python_source_hash as python_source_hash
|
|
1
3
|
from sarj_python_lint._ratchet_cli import run_ratchet as run_ratchet
|
|
2
4
|
from sarj_python_lint._source import python_source_encoding as python_source_encoding
|
|
3
5
|
from sarj_python_lint._version import __version__ as __version__
|
|
@@ -5,7 +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
|
+
from sarj_python_lint._source import python_source_lines, symbol_prefilter_source
|
|
9
9
|
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
|
|
10
10
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
11
11
|
from sarj_python_lint.rules._fastapi import FastapiIndex
|
|
@@ -47,7 +47,7 @@ class PythonFileContext:
|
|
|
47
47
|
|
|
48
48
|
@cached_property
|
|
49
49
|
def source_lines(self) -> list[str]:
|
|
50
|
-
return self.source
|
|
50
|
+
return python_source_lines(self.source)
|
|
51
51
|
|
|
52
52
|
@cached_property
|
|
53
53
|
def generated(self) -> bool:
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import AutofixPolicy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
from sarj_python_lint.rule_base import Fix
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def python_source_hash(source: str) -> str:
|
|
17
|
+
canonical = source.removeprefix("\ufeff").replace("\r\n", "\n").replace("\r", "\n")
|
|
18
|
+
return sha256(canonical.encode("utf-8")).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply_fixes(source: str, fixes: Sequence[Fix]) -> str:
|
|
22
|
+
if not fixes:
|
|
23
|
+
return source
|
|
24
|
+
digest = python_source_hash(source)
|
|
25
|
+
prefix = "\ufeff" if source.startswith("\ufeff") else ""
|
|
26
|
+
raw = source[len(prefix) :].encode("utf-8")
|
|
27
|
+
lines = raw.splitlines(keepends=True)
|
|
28
|
+
offsets = [0]
|
|
29
|
+
for line in lines:
|
|
30
|
+
offsets.append(offsets[-1] + len(line))
|
|
31
|
+
if not lines or lines[-1].endswith((b"\n", b"\r")):
|
|
32
|
+
lines.append(b"")
|
|
33
|
+
|
|
34
|
+
def offset(line: int, col: int) -> int:
|
|
35
|
+
if line < 1 or line > len(lines) or col < 1 or col > len(lines[line - 1].rstrip(b"\r\n")) + 1:
|
|
36
|
+
msg = "fix position is outside the source"
|
|
37
|
+
raise ValueError(msg)
|
|
38
|
+
prefix = lines[line - 1][: col - 1]
|
|
39
|
+
prefix.decode("utf-8")
|
|
40
|
+
return offsets[line - 1] + len(prefix)
|
|
41
|
+
|
|
42
|
+
edits: list[tuple[int, int, bytes]] = []
|
|
43
|
+
for fix in fixes:
|
|
44
|
+
if fix.safety is not AutofixPolicy.SAFE or fix.source_hash != digest or not fix.edits:
|
|
45
|
+
msg = "fix must be safe, nonempty and match the complete source revision"
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
for edit in fix.edits:
|
|
48
|
+
start = offset(edit.start_line, edit.start_col)
|
|
49
|
+
end = offset(edit.end_line, edit.end_col)
|
|
50
|
+
if end < start:
|
|
51
|
+
msg = "fix range runs backwards"
|
|
52
|
+
raise ValueError(msg)
|
|
53
|
+
edits.append((start, end, edit.replacement.encode("utf-8")))
|
|
54
|
+
edits.sort()
|
|
55
|
+
result: list[bytes] = []
|
|
56
|
+
previous = 0
|
|
57
|
+
for start, end, replacement in edits:
|
|
58
|
+
if start < previous:
|
|
59
|
+
msg = "fix groups overlap"
|
|
60
|
+
raise ValueError(msg)
|
|
61
|
+
result.extend((raw[previous:start], replacement))
|
|
62
|
+
previous = end
|
|
63
|
+
result.append(raw[previous:])
|
|
64
|
+
fixed = b"".join(result).decode("utf-8")
|
|
65
|
+
tree = ast.parse(fixed)
|
|
66
|
+
compile(tree, "<native-fix>", "exec")
|
|
67
|
+
return prefix + fixed
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from io import BytesIO
|
|
4
|
+
import re
|
|
4
5
|
import tokenize
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
7
|
import unicodedata
|
|
@@ -19,6 +20,11 @@ def read_python_source(path: Path) -> str:
|
|
|
19
20
|
return stream.read()
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def python_source_lines(source: str) -> list[str]:
|
|
24
|
+
lines = re.split(r"\r\n?|\n", source)
|
|
25
|
+
return lines[:-1] if not lines[-1] else lines
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def symbol_prefilter_source(source: str) -> str:
|
|
23
29
|
# Only necessary symbol gates may use this view; parse and locate original source.
|
|
24
30
|
return source if source.isascii() else unicodedata.normalize("NFKC", source)
|
|
@@ -75,6 +75,28 @@ class ColumnEncoding(StrEnum):
|
|
|
75
75
|
CODEPOINTS = "codepoints"
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class TextEdit:
|
|
80
|
+
"""An exact, half-open range of one-based lines and UTF-8 byte columns.
|
|
81
|
+
|
|
82
|
+
Columns refer to decoded source without its original leading BOM.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
start_line: int
|
|
86
|
+
start_col: int
|
|
87
|
+
end_line: int
|
|
88
|
+
end_col: int
|
|
89
|
+
replacement: str
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True, slots=True)
|
|
93
|
+
class Fix:
|
|
94
|
+
title: str
|
|
95
|
+
safety: AutofixPolicy
|
|
96
|
+
source_hash: str
|
|
97
|
+
edits: tuple[TextEdit, ...]
|
|
98
|
+
|
|
99
|
+
|
|
78
100
|
@dataclass(frozen=True, slots=True)
|
|
79
101
|
class Diagnostic:
|
|
80
102
|
path: Path
|
|
@@ -84,6 +106,7 @@ class Diagnostic:
|
|
|
84
106
|
message: str
|
|
85
107
|
severity: Severity = Severity.ERROR
|
|
86
108
|
column_encoding: ColumnEncoding = ColumnEncoding.UTF8_BYTES
|
|
109
|
+
fixes: tuple[Fix, ...] = ()
|
|
87
110
|
|
|
88
111
|
def format(self) -> str:
|
|
89
112
|
label = "warning: " if self.severity is Severity.WARNING else ""
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AnnotationOrigins:
|
|
14
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
15
|
+
self.context = context
|
|
16
|
+
self.provenance = context.static_resource_provenance
|
|
17
|
+
self.imports = {
|
|
18
|
+
node: ImportIndex.from_tree(ast.Module(body=[node], type_ignores=[]))
|
|
19
|
+
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def shadowed(self, name: str, at: ast.AST) -> bool:
|
|
23
|
+
return type_parameter_shadow(name, at, self.context)
|
|
24
|
+
|
|
25
|
+
def qualified(self, node: ast.expr, at: ast.AST) -> str | None:
|
|
26
|
+
root = node
|
|
27
|
+
while isinstance(root, ast.Attribute):
|
|
28
|
+
root = root.value
|
|
29
|
+
if (
|
|
30
|
+
not isinstance(root, ast.Name)
|
|
31
|
+
or root.id in self.provenance.mutated | self.provenance.external_bindings
|
|
32
|
+
or type_parameter_shadow(root.id, at, self.context)
|
|
33
|
+
):
|
|
34
|
+
return None
|
|
35
|
+
binding = self.provenance.binding(root.id, at)
|
|
36
|
+
parent = self.context.parents.get(binding) if binding is not None else None
|
|
37
|
+
index = self.imports.get(parent) if isinstance(parent, ast.Import | ast.ImportFrom) else None
|
|
38
|
+
return index.resolved_qualified_name(node) if index is not None else None
|
|
39
|
+
|
|
40
|
+
def builtin(self, node: ast.expr, name: str, at: ast.AST) -> bool:
|
|
41
|
+
return (
|
|
42
|
+
self.provenance.builtin_namespace_untouched
|
|
43
|
+
and isinstance(node, ast.Name)
|
|
44
|
+
and node.id == name
|
|
45
|
+
and not type_parameter_shadow(name, at, self.context)
|
|
46
|
+
and self.provenance.binding(name, at) is None
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def type_parameter_shadow(name: str, at: ast.AST, context: PythonFileContext) -> bool:
|
|
51
|
+
outside: set[ast.AST] = set()
|
|
52
|
+
current = at
|
|
53
|
+
while (parent := context.parents.get(current)) is not None:
|
|
54
|
+
if (
|
|
55
|
+
isinstance(parent, ast.arguments)
|
|
56
|
+
and (current in parent.defaults or current in parent.kw_defaults)
|
|
57
|
+
and (owner := context.parents.get(parent)) is not None
|
|
58
|
+
):
|
|
59
|
+
outside.add(owner)
|
|
60
|
+
if (
|
|
61
|
+
isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias)
|
|
62
|
+
and parent not in outside
|
|
63
|
+
and not (
|
|
64
|
+
isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
|
|
65
|
+
and current in parent.decorator_list
|
|
66
|
+
)
|
|
67
|
+
and any(
|
|
68
|
+
isinstance(parameter, ast.TypeVar | ast.ParamSpec | ast.TypeVarTuple) and parameter.name == name
|
|
69
|
+
for parameter in parent.type_params
|
|
70
|
+
)
|
|
71
|
+
):
|
|
72
|
+
return True
|
|
73
|
+
current = parent
|
|
74
|
+
return False
|
|
@@ -63,7 +63,7 @@ class AnnotationSemantics:
|
|
|
63
63
|
candidates[name] = value
|
|
64
64
|
if not candidates:
|
|
65
65
|
return cls(imports, MappingProxyType({}))
|
|
66
|
-
rebound =
|
|
66
|
+
rebound = module_rebindings(tree)
|
|
67
67
|
aliases = {name: value for name, value in candidates.items() if name not in ambiguous and name not in rebound}
|
|
68
68
|
return cls(imports, MappingProxyType(aliases))
|
|
69
69
|
|
|
@@ -139,7 +139,7 @@ class AnnotationSemantics:
|
|
|
139
139
|
return [node.slice]
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def
|
|
142
|
+
def module_rebindings(tree: ast.Module) -> set[str]:
|
|
143
143
|
counts: dict[str, int] = {}
|
|
144
144
|
for statement in tree.body:
|
|
145
145
|
for name in _bound_names(statement):
|
{sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
@@ -15,7 +15,32 @@ if TYPE_CHECKING:
|
|
|
15
15
|
_AST = ast.AST
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def statement_blocks(statements: list[ast.stmt]) -> Iterator[list[ast.stmt]]:
|
|
19
|
+
pending = [statements]
|
|
20
|
+
while pending:
|
|
21
|
+
block = pending.pop()
|
|
22
|
+
yield block
|
|
23
|
+
for statement in reversed(block):
|
|
24
|
+
match statement:
|
|
25
|
+
case ast.For() | ast.AsyncFor() | ast.While() | ast.If():
|
|
26
|
+
nested = [statement.body, statement.orelse]
|
|
27
|
+
case ast.With() | ast.AsyncWith():
|
|
28
|
+
nested = [statement.body]
|
|
29
|
+
case ast.Try() | ast.TryStar():
|
|
30
|
+
nested = [
|
|
31
|
+
statement.body,
|
|
32
|
+
*(handler.body for handler in statement.handlers),
|
|
33
|
+
statement.orelse,
|
|
34
|
+
statement.finalbody,
|
|
35
|
+
]
|
|
36
|
+
case ast.Match():
|
|
37
|
+
nested = [case.body for case in statement.cases]
|
|
38
|
+
case _:
|
|
39
|
+
continue
|
|
40
|
+
pending.extend(reversed(nested))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def object_list(value: object) -> TypeIs[list[object]]: # sarj-noqa: SARJ528 -- narrows opaque AST fields.
|
|
19
44
|
return isinstance(value, list)
|
|
20
45
|
|
|
21
46
|
|
|
@@ -28,27 +28,30 @@ class DeclaredClass:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _class_metadata_annotation(annotation: ast.expr, semantics: AnnotationSemantics) -> bool:
|
|
31
|
-
pending = [annotation]
|
|
32
31
|
seen: set[str] = set()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if node
|
|
32
|
+
node = semantics.parse(annotation)
|
|
33
|
+
while node is not None:
|
|
34
|
+
if isinstance(node, ast.Name) and node.id in semantics.aliases and node.id not in seen:
|
|
35
|
+
seen.add(node.id)
|
|
36
|
+
node = semantics.parse(semantics.aliases[node.id])
|
|
37
|
+
continue
|
|
38
|
+
if not isinstance(node, ast.Subscript):
|
|
39
|
+
return False
|
|
40
|
+
wrapper = semantics.imports.resolved_symbol(node.value, sources=_TYPING_SOURCES)
|
|
41
|
+
if wrapper in {"ClassVar", "Final"}:
|
|
42
|
+
return True
|
|
43
|
+
if wrapper != "Annotated":
|
|
44
|
+
return False
|
|
45
|
+
if isinstance(node.slice, ast.Tuple) and not node.slice.elts:
|
|
36
46
|
return True
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
member.value, sources=_TYPING_SOURCES
|
|
40
|
-
) in {"ClassVar", "Final"}:
|
|
41
|
-
return True
|
|
42
|
-
if isinstance(member, ast.Name) and member.id in semantics.aliases and member.id not in seen:
|
|
43
|
-
seen.add(member.id)
|
|
44
|
-
pending.append(semantics.aliases[member.id])
|
|
45
|
-
return False
|
|
47
|
+
node = semantics.parse(node.slice.elts[0] if isinstance(node.slice, ast.Tuple) else node.slice)
|
|
48
|
+
return True
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
class DeclaredContracts:
|
|
49
52
|
def __init__(self, context: PythonFileContext) -> None:
|
|
50
53
|
self.context = context
|
|
51
|
-
self._field_cache: dict[ast.ClassDef, dict[str, ast.AnnAssign]] = {}
|
|
54
|
+
self._field_cache: dict[tuple[ast.ClassDef, bool], dict[str, ast.AnnAssign]] = {}
|
|
52
55
|
self._module_annotations: dict[Path, AnnotationSemantics] = {}
|
|
53
56
|
self.project = context.session.project or ProjectIndexSet.single(context.path, context.source)
|
|
54
57
|
self.unit = self.project.unit(context.path)
|
|
@@ -130,23 +133,32 @@ class DeclaredContracts:
|
|
|
130
133
|
def inherits(self, model: DeclaredClass, bases: frozenset[str]) -> bool:
|
|
131
134
|
return self.project.class_inherits_from(model.unit, model.declaration.name, bases)
|
|
132
135
|
|
|
133
|
-
def model_fields(
|
|
136
|
+
def model_fields(
|
|
137
|
+
self, model: DeclaredClass, *, inherited: bool = False, seen: frozenset[ast.ClassDef] = frozenset()
|
|
138
|
+
) -> dict[str, ast.AnnAssign]:
|
|
134
139
|
if model.unit.tree is None:
|
|
135
140
|
return {}
|
|
136
|
-
|
|
141
|
+
key = (model.declaration, inherited)
|
|
142
|
+
cached = self._field_cache.get(key)
|
|
137
143
|
if cached is not None:
|
|
138
144
|
return cached
|
|
145
|
+
if model.declaration in seen or len(seen) >= _MAX_DEPTH:
|
|
146
|
+
return {}
|
|
139
147
|
semantics = self._module_semantics(model.unit)
|
|
140
|
-
fields = {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
fields: dict[str, ast.AnnAssign] = {}
|
|
149
|
+
if inherited and len(model.declaration.bases) == 1:
|
|
150
|
+
parent = self.class_type(model.declaration.bases[0], model.unit)
|
|
151
|
+
if parent is not None:
|
|
152
|
+
fields.update(self.model_fields(parent, inherited=True, seen=seen | {model.declaration}))
|
|
153
|
+
for node in model.declaration.body:
|
|
154
|
+
if not isinstance(node, ast.AnnAssign) or not isinstance(node.target, ast.Name):
|
|
155
|
+
continue
|
|
156
|
+
name = node.target.id
|
|
157
|
+
if name.startswith("_") or name == "model_config" or _class_metadata_annotation(node.annotation, semantics):
|
|
158
|
+
fields.pop(name, None)
|
|
159
|
+
else:
|
|
160
|
+
fields[name] = node
|
|
161
|
+
self._field_cache[key] = fields
|
|
150
162
|
return fields
|
|
151
163
|
|
|
152
164
|
def _module_semantics(self, unit: SourceUnit) -> AnnotationSemantics:
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint._fixes import python_source_hash
|
|
8
|
+
from sarj_python_lint._source import python_source_encoding
|
|
9
|
+
from sarj_python_lint.rule_base import AutofixPolicy, Fix, TextEdit
|
|
10
|
+
from sarj_python_lint.rules._suppression_comments import comment_lines
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
|
|
16
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_NAMESPACE_MUTATORS = frozenset(
|
|
20
|
+
{
|
|
21
|
+
"setattr",
|
|
22
|
+
"delattr",
|
|
23
|
+
"setitem",
|
|
24
|
+
"delitem",
|
|
25
|
+
"__setattr__",
|
|
26
|
+
"__delattr__",
|
|
27
|
+
"__setitem__",
|
|
28
|
+
"__delitem__",
|
|
29
|
+
"__init__",
|
|
30
|
+
"__ior__",
|
|
31
|
+
"ior",
|
|
32
|
+
"methodcaller",
|
|
33
|
+
"patch",
|
|
34
|
+
"clear",
|
|
35
|
+
"pop",
|
|
36
|
+
"popitem",
|
|
37
|
+
"setdefault",
|
|
38
|
+
"update",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LocalAliasFixes:
|
|
44
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
45
|
+
self.context = context
|
|
46
|
+
|
|
47
|
+
@cached_property
|
|
48
|
+
def _lines(self) -> tuple[bytes, ...]:
|
|
49
|
+
return tuple(line.encode("utf-8") for line in self.context.source_lines)
|
|
50
|
+
|
|
51
|
+
def spelling(self, node: ast.Name) -> str:
|
|
52
|
+
return self._lines[node.lineno - 1][node.col_offset : node.end_col_offset].decode("utf-8")
|
|
53
|
+
|
|
54
|
+
@cached_property
|
|
55
|
+
def _available(self) -> bool:
|
|
56
|
+
context = self.context
|
|
57
|
+
return (
|
|
58
|
+
python_source_encoding(context.source.encode("utf-8")) in {"utf-8", "utf-8-sig"}
|
|
59
|
+
and context.imports.builtin_is_unshadowed("dict")
|
|
60
|
+
and not any("dict" in bindings for bindings in context.resource_provenance.bindings.values())
|
|
61
|
+
and context.resource_provenance.builtin_namespace_untouched
|
|
62
|
+
and not any(node.attr == "__code__" for node in context.nodes(ast.Attribute))
|
|
63
|
+
and not _visible_namespace_mutation(context)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@cached_property
|
|
67
|
+
def _comments(self) -> Mapping[int, str]:
|
|
68
|
+
return comment_lines(tuple(self.context.source_lines))
|
|
69
|
+
|
|
70
|
+
@cached_property
|
|
71
|
+
def _digest(self) -> str:
|
|
72
|
+
return python_source_hash(self.context.source)
|
|
73
|
+
|
|
74
|
+
def for_alias(self, assignment: ast.Assign, use: ast.Name) -> tuple[Fix, ...]:
|
|
75
|
+
context = self.context
|
|
76
|
+
owner = context.parents.get(assignment)
|
|
77
|
+
if not isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
78
|
+
return ()
|
|
79
|
+
if not isinstance(context.parents.get(owner), ast.Module) or owner.decorator_list or owner.type_params:
|
|
80
|
+
return ()
|
|
81
|
+
args = owner.args
|
|
82
|
+
parameters = [*args.posonlyargs, *args.args, *args.kwonlyargs]
|
|
83
|
+
if len(parameters) != 1 or args.vararg or args.kwarg or args.defaults or any(args.kw_defaults):
|
|
84
|
+
return ()
|
|
85
|
+
source = assignment.value
|
|
86
|
+
if not isinstance(source, ast.Name) or parameters[0].arg != source.id:
|
|
87
|
+
return ()
|
|
88
|
+
body = owner.body[1:] if ast.get_docstring(owner) is not None else owner.body
|
|
89
|
+
if body[:1] != [assignment] or len(body[1:]) != 1 or not isinstance(body[-1], ast.Return):
|
|
90
|
+
return ()
|
|
91
|
+
call = body[-1].value
|
|
92
|
+
if not isinstance(call, ast.Call) or not isinstance(call.func, ast.Name) or call.func.id != "dict":
|
|
93
|
+
return ()
|
|
94
|
+
if call.args or len(call.keywords) != 1 or call.keywords[0].value is not use or not self._available:
|
|
95
|
+
return ()
|
|
96
|
+
edits = self._edits(assignment, use, source=source, owner=owner)
|
|
97
|
+
if not edits:
|
|
98
|
+
return ()
|
|
99
|
+
return (Fix("Pass the original local directly", AutofixPolicy.SAFE, self._digest, edits),)
|
|
100
|
+
|
|
101
|
+
def _edits(
|
|
102
|
+
self,
|
|
103
|
+
assignment: ast.Assign,
|
|
104
|
+
use: ast.Name,
|
|
105
|
+
*,
|
|
106
|
+
source: ast.Name,
|
|
107
|
+
owner: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
108
|
+
) -> tuple[TextEdit, ...]:
|
|
109
|
+
if any(line in self._comments for line in range(owner.lineno, (owner.end_lineno or owner.lineno) + 1)):
|
|
110
|
+
return ()
|
|
111
|
+
if assignment.end_lineno != assignment.lineno:
|
|
112
|
+
return ()
|
|
113
|
+
line = self.context.source_lines[assignment.lineno - 1]
|
|
114
|
+
raw = self._lines[assignment.lineno - 1]
|
|
115
|
+
if (
|
|
116
|
+
raw[: assignment.col_offset].strip()
|
|
117
|
+
or raw[assignment.end_col_offset :].strip()
|
|
118
|
+
or ";" in line
|
|
119
|
+
or "\\" in line
|
|
120
|
+
):
|
|
121
|
+
return ()
|
|
122
|
+
return (
|
|
123
|
+
TextEdit(assignment.lineno, 1, assignment.lineno + 1, 1, ""),
|
|
124
|
+
TextEdit(
|
|
125
|
+
use.lineno,
|
|
126
|
+
use.col_offset + 1,
|
|
127
|
+
use.end_lineno or use.lineno,
|
|
128
|
+
(use.end_col_offset or 0) + 1,
|
|
129
|
+
self.spelling(source),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _visible_namespace_mutation(context: PythonFileContext) -> bool:
|
|
135
|
+
if _imports_unittest(context):
|
|
136
|
+
return True
|
|
137
|
+
for node in context.nodes(ast.Name, ast.alias, ast.Attribute, ast.Subscript, ast.AugAssign):
|
|
138
|
+
match node:
|
|
139
|
+
case ast.Name(id=name) | ast.alias(name=name):
|
|
140
|
+
if name in _NAMESPACE_MUTATORS:
|
|
141
|
+
return True
|
|
142
|
+
case ast.Attribute(attr=name, ctx=ctx):
|
|
143
|
+
if name in _NAMESPACE_MUTATORS or not isinstance(ctx, ast.Load):
|
|
144
|
+
return True
|
|
145
|
+
case ast.Subscript(ctx=ctx):
|
|
146
|
+
if not isinstance(ctx, ast.Load):
|
|
147
|
+
return True
|
|
148
|
+
case ast.AugAssign(op=op):
|
|
149
|
+
if isinstance(op, ast.BitOr):
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _imports_unittest(context: PythonFileContext) -> bool:
|
|
155
|
+
for node in context.nodes(ast.Import):
|
|
156
|
+
if any(alias.name.partition(".")[0] == "unittest" for alias in node.names):
|
|
157
|
+
return True
|
|
158
|
+
return any(
|
|
159
|
+
node.module is not None and node.module.partition(".")[0] == "unittest"
|
|
160
|
+
for node in context.nodes(ast.ImportFrom)
|
|
161
|
+
)
|
{sarj_python_lint-1.70.1 → sarj_python_lint-1.72.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
@@ -5,6 +5,7 @@ from collections import Counter
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from sarj_python_lint.rules._declared_contracts import DeclaredClass
|
|
8
9
|
from sarj_python_lint.rules._project_index import ProjectIndexSet, SourceUnit, SymbolRef
|
|
9
10
|
|
|
10
11
|
|
|
@@ -40,9 +41,7 @@ def direct_nodes(node: ast.AST) -> Iterator[ast.AST]:
|
|
|
40
41
|
yield from direct_nodes(child)
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
def _class_owner(
|
|
44
|
-
context: PythonFileContext, expression: ast.expr, project: ProjectIndexSet
|
|
45
|
-
) -> tuple[SourceUnit, ast.ClassDef] | None:
|
|
44
|
+
def _class_owner(context: PythonFileContext, expression: ast.expr, project: ProjectIndexSet) -> DeclaredClass | None:
|
|
46
45
|
qualified = context.module_imports.resolved_qualified_name(expression)
|
|
47
46
|
unit = project.unit(context.path)
|
|
48
47
|
if qualified is not None:
|
|
@@ -62,7 +61,7 @@ def _class_owner(
|
|
|
62
61
|
for node in direct_nodes(unit.tree)
|
|
63
62
|
):
|
|
64
63
|
return None
|
|
65
|
-
return unit, owner
|
|
64
|
+
return DeclaredClass(unit, owner)
|
|
66
65
|
|
|
67
66
|
|
|
68
67
|
def constructor(
|
|
@@ -75,7 +74,7 @@ def constructor(
|
|
|
75
74
|
resolved = _class_owner(context, expression, project)
|
|
76
75
|
if resolved is None:
|
|
77
76
|
return None
|
|
78
|
-
unit, owner = resolved
|
|
77
|
+
unit, owner = resolved.unit, resolved.declaration
|
|
79
78
|
if unit.module is None:
|
|
80
79
|
return None
|
|
81
80
|
initializer = next(
|
|
@@ -240,15 +239,9 @@ def wiring(
|
|
|
240
239
|
spec: Constructor,
|
|
241
240
|
scope: WiringScope,
|
|
242
241
|
) -> tuple[tuple[str, str], ...] | None:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if len(call.args) > len(spec.positional):
|
|
242
|
+
values = call_arguments(call, spec.positional)
|
|
243
|
+
if values is None:
|
|
246
244
|
return None
|
|
247
|
-
values = dict(zip(spec.positional, call.args, strict=False))
|
|
248
|
-
for keyword in call.keywords:
|
|
249
|
-
if keyword.arg is None or keyword.arg in values:
|
|
250
|
-
return None
|
|
251
|
-
values[keyword.arg] = keyword.value
|
|
252
245
|
result: list[tuple[str, str]] = []
|
|
253
246
|
for name in sorted(spec.collaborators):
|
|
254
247
|
value = values.get(name)
|
|
@@ -261,6 +254,19 @@ def wiring(
|
|
|
261
254
|
return tuple(result)
|
|
262
255
|
|
|
263
256
|
|
|
257
|
+
def call_arguments(call: ast.Call, positional: tuple[str, ...]) -> dict[str, ast.expr] | None:
|
|
258
|
+
if any(isinstance(arg, ast.Starred) for arg in call.args) or any(keyword.arg is None for keyword in call.keywords):
|
|
259
|
+
return None
|
|
260
|
+
if len(call.args) > len(positional):
|
|
261
|
+
return None
|
|
262
|
+
values = dict(zip(positional, call.args, strict=False))
|
|
263
|
+
for keyword in call.keywords:
|
|
264
|
+
if keyword.arg is None or keyword.arg in values:
|
|
265
|
+
return None
|
|
266
|
+
values[keyword.arg] = keyword.value
|
|
267
|
+
return values
|
|
268
|
+
|
|
269
|
+
|
|
264
270
|
def _value_key(
|
|
265
271
|
context: PythonFileContext,
|
|
266
272
|
project: ProjectIndexSet,
|
|
@@ -300,4 +306,4 @@ def _value_key(
|
|
|
300
306
|
]
|
|
301
307
|
if any(key is None for key in positional) or any(key is None for _, key in keywords):
|
|
302
308
|
return None
|
|
303
|
-
return f"{owner
|
|
309
|
+
return f"{owner.unit.module}.{owner.declaration.name}:{positional!r}:{sorted(keywords)!r}"
|