sarj-python-lint 1.66.1__tar.gz → 1.66.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/PKG-INFO +1 -1
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/pyproject.toml +1 -1
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_annotation_semantics.py +59 -26
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_imports.py +16 -1
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_resource_provenance.py +30 -13
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +1 -12
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_async_function_cache.py +1 -11
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_loguru_exc_info.py +1 -11
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +67 -24
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +1 -6
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +1 -6
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -12
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +5 -3
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_str_enum.py +1 -12
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +1 -6
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +9 -3
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/.gitignore +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/LICENSE +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/README.md +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/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.66.
|
|
3
|
+
Version: 1.66.2
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import ast
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from types import MappingProxyType
|
|
6
|
-
from typing import Self
|
|
6
|
+
from typing import NamedTuple, Self
|
|
7
7
|
|
|
8
8
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
9
9
|
|
|
@@ -12,6 +12,23 @@ _TYPING_SOURCES = frozenset({"typing", "typing_extensions"})
|
|
|
12
12
|
_TRANSPARENT_WRAPPERS = frozenset({"Annotated", "ClassVar", "Final", "NotRequired", "Optional", "ReadOnly", "Required"})
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def bit_or_members(expression: ast.expr) -> list[ast.expr]:
|
|
16
|
+
members: list[ast.expr] = []
|
|
17
|
+
pending = [expression]
|
|
18
|
+
while pending:
|
|
19
|
+
member = pending.pop()
|
|
20
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
21
|
+
pending.extend((member.right, member.left))
|
|
22
|
+
else:
|
|
23
|
+
members.append(member)
|
|
24
|
+
return members
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _AnnotationMember(NamedTuple):
|
|
28
|
+
node: ast.expr | None
|
|
29
|
+
seen: frozenset[str]
|
|
30
|
+
|
|
31
|
+
|
|
15
32
|
@dataclass(frozen=True, slots=True)
|
|
16
33
|
class AnnotationSemantics:
|
|
17
34
|
imports: ImportIndex
|
|
@@ -75,35 +92,51 @@ class AnnotationSemantics:
|
|
|
75
92
|
if resolved is None:
|
|
76
93
|
return ()
|
|
77
94
|
if isinstance(resolved, ast.Name) and resolved.id in self.aliases:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
pending = [_AnnotationMember(resolved, seen)]
|
|
96
|
+
else:
|
|
97
|
+
children = self._transparent_children(resolved)
|
|
98
|
+
if children is None:
|
|
99
|
+
return (resolved,)
|
|
100
|
+
pending = [_AnnotationMember(child, seen) for child in reversed(children)]
|
|
101
|
+
members: list[ast.expr] = []
|
|
102
|
+
while pending:
|
|
103
|
+
state = self._resolve_member(pending.pop())
|
|
104
|
+
if state.node is None:
|
|
105
|
+
continue
|
|
106
|
+
children = self._transparent_children(state.node)
|
|
107
|
+
if children is None:
|
|
108
|
+
members.append(state.node)
|
|
109
|
+
else:
|
|
110
|
+
pending.extend(_AnnotationMember(child, state.seen) for child in reversed(children))
|
|
111
|
+
return tuple(members)
|
|
91
112
|
|
|
92
|
-
def
|
|
113
|
+
def _resolve_member(self, state: _AnnotationMember) -> _AnnotationMember:
|
|
114
|
+
node = self.parse(state.node)
|
|
115
|
+
if not isinstance(node, ast.Name) or node.id not in self.aliases:
|
|
116
|
+
return _AnnotationMember(node, state.seen)
|
|
117
|
+
seen = set(state.seen)
|
|
118
|
+
while isinstance(node, ast.Name) and node.id in self.aliases:
|
|
119
|
+
if node.id in seen:
|
|
120
|
+
return _AnnotationMember(None, frozenset(seen))
|
|
121
|
+
seen.add(node.id)
|
|
122
|
+
node = self.parse(self.aliases[node.id])
|
|
123
|
+
return _AnnotationMember(node, frozenset(seen))
|
|
124
|
+
|
|
125
|
+
def _transparent_children(self, node: ast.expr) -> list[ast.expr] | None:
|
|
126
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
127
|
+
return bit_or_members(node)
|
|
128
|
+
if not isinstance(node, ast.Subscript):
|
|
129
|
+
return None
|
|
93
130
|
wrapper = self.imports.resolved_symbol(node.value, sources=_TYPING_SOURCES)
|
|
94
131
|
if wrapper == "Union":
|
|
95
|
-
|
|
96
|
-
return self._flatten_members(members, seen=seen)
|
|
132
|
+
return list(node.slice.elts) if isinstance(node.slice, ast.Tuple) else [node.slice]
|
|
97
133
|
if wrapper not in _TRANSPARENT_WRAPPERS:
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for node in nodes:
|
|
105
|
-
members.extend(self.transparent_members(node, seen=seen))
|
|
106
|
-
return tuple(members)
|
|
134
|
+
return None
|
|
135
|
+
if isinstance(node.slice, ast.Tuple):
|
|
136
|
+
if not node.slice.elts:
|
|
137
|
+
return None
|
|
138
|
+
return [node.slice.elts[0]]
|
|
139
|
+
return [node.slice]
|
|
107
140
|
|
|
108
141
|
|
|
109
142
|
def _module_rebindings(tree: ast.Module) -> set[str]:
|
|
@@ -4,11 +4,15 @@ import ast
|
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from types import MappingProxyType
|
|
7
|
-
from typing import Self
|
|
7
|
+
from typing import TYPE_CHECKING, Self
|
|
8
8
|
|
|
9
9
|
from sarj_python_lint.rules._ast_index import comprehension_results, walk as walk_ast
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
ABC_SOURCES = frozenset({"abc"})
|
|
13
17
|
TYPING_SOURCES = frozenset({"typing", "typing_extensions"})
|
|
14
18
|
|
|
@@ -81,6 +85,17 @@ class ImportIndex:
|
|
|
81
85
|
return None
|
|
82
86
|
|
|
83
87
|
|
|
88
|
+
def ambiguous_import_names(context: PythonFileContext) -> frozenset[str]:
|
|
89
|
+
return frozenset(
|
|
90
|
+
alias.asname or alias.name.partition(".")[0]
|
|
91
|
+
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
92
|
+
for alias in node.names
|
|
93
|
+
if context.parents[node] is not context.tree
|
|
94
|
+
or (isinstance(node, ast.ImportFrom) and node.level > 0)
|
|
95
|
+
or alias.name == "*"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
84
99
|
def _non_import_bindings(tree: ast.Module) -> set[str]:
|
|
85
100
|
names: set[str] = set()
|
|
86
101
|
for node in walk_ast(tree):
|
|
@@ -12,6 +12,7 @@ from sarj_python_lint.rules._runtime_bindings import RuntimeBindings
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Callable
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
from sarj_python_lint._file_context import PythonFileContext
|
|
@@ -82,6 +83,8 @@ class ResourceProvenance:
|
|
|
82
83
|
self.bindings: dict[ast.AST | None, dict[str, list[ast.AST]]] = defaultdict(lambda: defaultdict(list))
|
|
83
84
|
self.reads: dict[str, list[ast.Name]] = defaultdict(list)
|
|
84
85
|
self._reads_by_binding: dict[str, dict[ast.AST, list[ast.Name]]] = {}
|
|
86
|
+
self._runtime_reads_by_binding: dict[str, dict[ast.AST, list[ast.Name]]] = {}
|
|
87
|
+
self._import_read_safety: dict[str, bool] = {}
|
|
85
88
|
self.escape_cache: dict[tuple[ast.AST, bool], bool] = {}
|
|
86
89
|
self.async_contract_cache: dict[ast.AsyncFunctionDef, bool] = {}
|
|
87
90
|
self.import_contract_cache: dict[str, bool] = {}
|
|
@@ -172,10 +175,10 @@ class ResourceProvenance:
|
|
|
172
175
|
if qualified is None or not self._owner_namespace_untouched(qualified.partition(".")[0]):
|
|
173
176
|
return None
|
|
174
177
|
if binding not in self.runtime_import_cache:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
for read in self.reads.get(root, ())
|
|
178
|
+
reads = self._indexed_reads(
|
|
179
|
+
root, binding, cache=self._runtime_reads_by_binding, resolve=self.runtime_binding
|
|
178
180
|
)
|
|
181
|
+
self.runtime_import_cache[binding] = all(self._safe_import_use(read) for read in reads)
|
|
179
182
|
return qualified if self.runtime_import_cache[binding] else None
|
|
180
183
|
|
|
181
184
|
@cached_property
|
|
@@ -283,18 +286,22 @@ class ResourceProvenance:
|
|
|
283
286
|
):
|
|
284
287
|
return False
|
|
285
288
|
binding = self.binding(root, node)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if isinstance(parent, ast.Attribute | ast.ExceptHandler):
|
|
289
|
-
continue
|
|
290
|
-
if not self._safe_import_use(read):
|
|
291
|
-
return False
|
|
289
|
+
if not self._import_reads_safe(root):
|
|
290
|
+
return False
|
|
292
291
|
return (
|
|
293
292
|
isinstance(binding, ast.alias)
|
|
294
293
|
and isinstance(self.scope(binding), ast.Module)
|
|
295
294
|
and self.context.imports.resolved_qualified_name(node) == qualified
|
|
296
295
|
)
|
|
297
296
|
|
|
297
|
+
def _import_reads_safe(self, name: str) -> bool:
|
|
298
|
+
if name not in self._import_read_safety:
|
|
299
|
+
self._import_read_safety[name] = all(
|
|
300
|
+
isinstance(self.parents.get(read), ast.Attribute | ast.ExceptHandler) or self._safe_import_use(read)
|
|
301
|
+
for read in self.reads.get(name, ())
|
|
302
|
+
)
|
|
303
|
+
return self._import_read_safety[name]
|
|
304
|
+
|
|
298
305
|
def _owner_namespace_untouched(self, module: str) -> bool:
|
|
299
306
|
if module not in _RESOURCE_OWNERS:
|
|
300
307
|
return True
|
|
@@ -459,13 +466,23 @@ class ResourceProvenance:
|
|
|
459
466
|
)
|
|
460
467
|
|
|
461
468
|
def _binding_reads(self, name: str, binding: ast.AST) -> list[ast.Name]:
|
|
462
|
-
|
|
469
|
+
return self._indexed_reads(name, binding, cache=self._reads_by_binding, resolve=self.binding)
|
|
470
|
+
|
|
471
|
+
def _indexed_reads(
|
|
472
|
+
self,
|
|
473
|
+
name: str,
|
|
474
|
+
binding: ast.AST,
|
|
475
|
+
*,
|
|
476
|
+
cache: dict[str, dict[ast.AST, list[ast.Name]]],
|
|
477
|
+
resolve: Callable[[str, ast.AST], ast.AST | None],
|
|
478
|
+
) -> list[ast.Name]:
|
|
479
|
+
if name not in cache:
|
|
463
480
|
grouped: dict[ast.AST, list[ast.Name]] = defaultdict(list)
|
|
464
481
|
for read in self.reads.get(name, ()):
|
|
465
|
-
if (owner :=
|
|
482
|
+
if (owner := resolve(read.id, read)) is not None:
|
|
466
483
|
grouped[owner].append(read)
|
|
467
|
-
|
|
468
|
-
return
|
|
484
|
+
cache[name] = grouped
|
|
485
|
+
return cache[name].get(binding, [])
|
|
469
486
|
|
|
470
487
|
def unescaped(self, name: ast.Name, at: ast.AST, *, allow_context: bool = False) -> bool:
|
|
471
488
|
binding = self.binding(name.id, at)
|
|
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
RuleDocumentation,
|
|
17
17
|
RuleExample,
|
|
18
18
|
)
|
|
19
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _bit_union_members
|
|
19
20
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
20
21
|
from sarj_python_lint.rules._paths import is_test_path
|
|
21
22
|
|
|
@@ -314,18 +315,6 @@ def _literal_key(value: object) -> _LiteralKey:
|
|
|
314
315
|
return _LiteralKey(type(value), value)
|
|
315
316
|
|
|
316
317
|
|
|
317
|
-
def _bit_union_members(node: ast.expr) -> list[ast.expr]:
|
|
318
|
-
members: list[ast.expr] = []
|
|
319
|
-
pending = [node]
|
|
320
|
-
while pending:
|
|
321
|
-
member = pending.pop()
|
|
322
|
-
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
323
|
-
pending.extend((member.right, member.left))
|
|
324
|
-
else:
|
|
325
|
-
members.append(member)
|
|
326
|
-
return members
|
|
327
|
-
|
|
328
|
-
|
|
329
318
|
def _literal_domain(node: ast.expr, imports: ImportIndex) -> _LiteralDomain | None:
|
|
330
319
|
node = _parse_forward_annotation(node)
|
|
331
320
|
if isinstance(node, ast.Constant) and node.value is None:
|
|
@@ -17,6 +17,7 @@ from sarj_python_lint.rule_base import (
|
|
|
17
17
|
is_suppressed,
|
|
18
18
|
)
|
|
19
19
|
from sarj_python_lint.rules._ast_index import has_own_yield
|
|
20
|
+
from sarj_python_lint.rules._imports import ambiguous_import_names as _nested_import_names
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
@@ -114,17 +115,6 @@ class NoAsyncFunctionCache(Rule):
|
|
|
114
115
|
return diagnostics
|
|
115
116
|
|
|
116
117
|
|
|
117
|
-
def _nested_import_names(context: PythonFileContext) -> frozenset[str]:
|
|
118
|
-
return frozenset(
|
|
119
|
-
alias.asname or alias.name.partition(".")[0]
|
|
120
|
-
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
121
|
-
for alias in node.names
|
|
122
|
-
if context.parents[node] is not context.tree
|
|
123
|
-
or (isinstance(node, ast.ImportFrom) and node.level > 0)
|
|
124
|
-
or alias.name == "*"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
118
|
def _is_coroutine_cache(decorator: ast.expr, imports: ImportIndex, shadowed: frozenset[str]) -> bool:
|
|
129
119
|
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
130
120
|
root = target
|
{sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_loguru_exc_info.py
RENAMED
|
@@ -17,6 +17,7 @@ from sarj_python_lint.rule_base import (
|
|
|
17
17
|
Severity,
|
|
18
18
|
is_suppressed,
|
|
19
19
|
)
|
|
20
|
+
from sarj_python_lint.rules._imports import ambiguous_import_names as _ambiguous_import_names
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
@@ -114,17 +115,6 @@ class NoLoguruExcInfo(Rule):
|
|
|
114
115
|
return diagnostics
|
|
115
116
|
|
|
116
117
|
|
|
117
|
-
def _ambiguous_import_names(context: PythonFileContext) -> frozenset[str]:
|
|
118
|
-
return frozenset(
|
|
119
|
-
alias.asname or alias.name.partition(".")[0]
|
|
120
|
-
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
121
|
-
for alias in node.names
|
|
122
|
-
if context.parents[node] is not context.tree
|
|
123
|
-
or (isinstance(node, ast.ImportFrom) and node.level > 0)
|
|
124
|
-
or alias.name == "*"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
118
|
def _is_unconfigured_loguru(receiver: ast.expr, context: PythonFileContext, shadowed: frozenset[str]) -> bool:
|
|
129
119
|
opted = False
|
|
130
120
|
while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
|
|
@@ -21,7 +21,7 @@ from sarj_python_lint.rules._logging import LOG_METHODS, is_logger_expr
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
|
-
from collections.abc import Mapping
|
|
24
|
+
from collections.abc import Mapping, Sequence
|
|
25
25
|
|
|
26
26
|
from sarj_python_lint._file_context import PythonFileContext
|
|
27
27
|
|
|
@@ -101,6 +101,7 @@ _STRING_METHODS = frozenset(
|
|
|
101
101
|
}
|
|
102
102
|
)
|
|
103
103
|
_REGEX_METHODS = frozenset({"compile", "findall", "finditer", "fullmatch", "match", "search", "split", "sub", "subn"})
|
|
104
|
+
_REGEX_CALL_NAMES = frozenset(f"re.{name}" for name in _REGEX_METHODS)
|
|
104
105
|
_COMPACT_UNITS = frozenset({"em", "ms", "pt", "px", "rem", "s"})
|
|
105
106
|
_PRODUCER_NAMES = _STRING_CALLS | {"json", "re", "str"}
|
|
106
107
|
|
|
@@ -114,6 +115,7 @@ class _StringEvidence(NamedTuple):
|
|
|
114
115
|
class _RegexBindings(NamedTuple):
|
|
115
116
|
modules: frozenset[str]
|
|
116
117
|
functions: frozenset[str]
|
|
118
|
+
scoped_names: frozenset[str]
|
|
117
119
|
|
|
118
120
|
|
|
119
121
|
class PreferFstringOverConcat(Rule):
|
|
@@ -188,9 +190,9 @@ class PreferFstringOverConcat(Rule):
|
|
|
188
190
|
excluded: set[int] = set()
|
|
189
191
|
adds: list[ast.BinOp] = []
|
|
190
192
|
parents = context.parents
|
|
191
|
-
regex_bindings = _regex_bindings(
|
|
193
|
+
regex_bindings = _regex_bindings(context)
|
|
192
194
|
for node in context.nodes(ast.BinOp, ast.Call, ast.JoinedStr):
|
|
193
|
-
_classify_concat_context(node, inner, excluded, adds, regex_bindings)
|
|
195
|
+
_classify_concat_context(node, inner, excluded, adds, regex_bindings, context=context)
|
|
194
196
|
|
|
195
197
|
diags: list[Diagnostic] = []
|
|
196
198
|
module_evidence: _StringEvidence | None = None
|
|
@@ -226,6 +228,8 @@ def _classify_concat_context(
|
|
|
226
228
|
excluded: set[int],
|
|
227
229
|
adds: list[ast.BinOp],
|
|
228
230
|
regex_bindings: _RegexBindings,
|
|
231
|
+
*,
|
|
232
|
+
context: PythonFileContext,
|
|
229
233
|
) -> None:
|
|
230
234
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
231
235
|
_collect_add_chain(node, adds, inner)
|
|
@@ -233,9 +237,9 @@ def _classify_concat_context(
|
|
|
233
237
|
# `("%0" + width + "d") % args` builds a format template.
|
|
234
238
|
excluded.add(id(node.left))
|
|
235
239
|
elif isinstance(node, ast.Call) and _is_logging_call(node):
|
|
236
|
-
|
|
237
|
-
elif isinstance(node, ast.Call) and _is_regex_call(node, regex_bindings):
|
|
238
|
-
|
|
240
|
+
_exclude_call_chains(node, excluded)
|
|
241
|
+
elif isinstance(node, ast.Call) and _is_regex_call(node, regex_bindings, context):
|
|
242
|
+
_exclude_regex_chains(node, excluded, context)
|
|
239
243
|
elif isinstance(node, ast.JoinedStr):
|
|
240
244
|
# Rewriting a concat already nested in an f-string merely
|
|
241
245
|
# trades one interpolation expression for a nested f-string.
|
|
@@ -249,26 +253,75 @@ def _is_logging_call(node: ast.Call) -> bool:
|
|
|
249
253
|
return is_logger_expr(func.value)
|
|
250
254
|
|
|
251
255
|
|
|
252
|
-
def _is_regex_call(node: ast.Call, bindings: _RegexBindings) -> bool:
|
|
256
|
+
def _is_regex_call(node: ast.Call, bindings: _RegexBindings, context: PythonFileContext) -> bool:
|
|
253
257
|
func = node.func
|
|
254
|
-
|
|
258
|
+
if isinstance(func, ast.Name) and func.id in bindings.functions:
|
|
259
|
+
return True
|
|
260
|
+
if (
|
|
255
261
|
isinstance(func, ast.Attribute)
|
|
256
262
|
and isinstance(func.value, ast.Name)
|
|
257
263
|
and func.value.id in bindings.modules
|
|
258
264
|
and func.attr in _REGEX_METHODS
|
|
259
|
-
)
|
|
265
|
+
):
|
|
266
|
+
return True
|
|
267
|
+
root = func.value if isinstance(func, ast.Attribute) else func
|
|
268
|
+
if not isinstance(root, ast.Name) or root.id not in bindings.scoped_names:
|
|
269
|
+
return False
|
|
270
|
+
return _proven_regex_call(node, context)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _proven_regex_call(node: ast.Call, context: PythonFileContext) -> bool:
|
|
274
|
+
arguments: list[ast.expr] = [*node.args, *(keyword.value for keyword in node.keywords)]
|
|
275
|
+
if not any(isinstance(part, ast.BinOp) and isinstance(part.op, ast.Add) for arg in arguments for part in walk(arg)):
|
|
276
|
+
return False
|
|
277
|
+
return context.static_resource_provenance.runtime_import_name(node.func) in _REGEX_CALL_NAMES
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _exclude_regex_chains(node: ast.Call, excluded: set[int], context: PythonFileContext) -> None:
|
|
281
|
+
if node.keywords and _proven_regex_call(node, context):
|
|
282
|
+
_exclude_call_chains(node, excluded)
|
|
283
|
+
else:
|
|
284
|
+
excluded.update(id(sub) for arg in node.args for sub in walk(arg))
|
|
260
285
|
|
|
261
286
|
|
|
262
|
-
def _regex_bindings(
|
|
287
|
+
def _regex_bindings(context: PythonFileContext) -> _RegexBindings:
|
|
288
|
+
imports = context.nodes(ast.Import, ast.ImportFrom)
|
|
289
|
+
module_imports = [node for node in imports if context.parents[node] is context.tree]
|
|
263
290
|
modules = {
|
|
264
291
|
alias.asname or alias.name
|
|
265
|
-
for statement in
|
|
292
|
+
for statement in module_imports
|
|
266
293
|
if isinstance(statement, ast.Import)
|
|
267
294
|
for alias in statement.names
|
|
268
295
|
if alias.name == "re"
|
|
269
296
|
}
|
|
270
|
-
functions = _regex_function_bindings(
|
|
271
|
-
return _RegexBindings(frozenset(modules), frozenset(functions))
|
|
297
|
+
functions = _regex_function_bindings(module_imports)
|
|
298
|
+
return _RegexBindings(frozenset(modules), frozenset(functions), _scoped_regex_names(context, imports))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _regex_function_bindings(imports: Sequence[ast.Import | ast.ImportFrom]) -> set[str]:
|
|
302
|
+
return {
|
|
303
|
+
alias.asname or alias.name
|
|
304
|
+
for statement in imports
|
|
305
|
+
if isinstance(statement, ast.ImportFrom) and statement.module == "re"
|
|
306
|
+
for alias in statement.names
|
|
307
|
+
if alias.name in _REGEX_METHODS
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _scoped_regex_names(context: PythonFileContext, imports: Sequence[ast.Import | ast.ImportFrom]) -> frozenset[str]:
|
|
312
|
+
return frozenset(
|
|
313
|
+
alias.asname or alias.name
|
|
314
|
+
for node in imports
|
|
315
|
+
if context.parents[node] is not context.tree
|
|
316
|
+
for alias in node.names
|
|
317
|
+
if (isinstance(node, ast.Import) and alias.name == "re")
|
|
318
|
+
or (
|
|
319
|
+
isinstance(node, ast.ImportFrom)
|
|
320
|
+
and node.module == "re"
|
|
321
|
+
and node.level == 0
|
|
322
|
+
and alias.name in _REGEX_METHODS
|
|
323
|
+
)
|
|
324
|
+
)
|
|
272
325
|
|
|
273
326
|
|
|
274
327
|
def _in_callable_scope(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
|
@@ -603,16 +656,6 @@ def _stored_names(node: ast.AST) -> set[str]:
|
|
|
603
656
|
}
|
|
604
657
|
|
|
605
658
|
|
|
606
|
-
def _regex_function_bindings(tree: ast.Module) -> set[str]:
|
|
607
|
-
return {
|
|
608
|
-
alias.asname or alias.name
|
|
609
|
-
for statement in tree.body
|
|
610
|
-
if isinstance(statement, ast.ImportFrom) and statement.module == "re"
|
|
611
|
-
for alias in statement.names
|
|
612
|
-
if alias.name in _REGEX_METHODS
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
659
|
def _collect_string_parameters(
|
|
617
660
|
scope: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda, shadowed: frozenset[str], known: set[str]
|
|
618
661
|
) -> None:
|
|
@@ -657,7 +700,7 @@ def _update_string_bindings(
|
|
|
657
700
|
known.difference_update(_bound_names(statement))
|
|
658
701
|
|
|
659
702
|
|
|
660
|
-
def
|
|
703
|
+
def _exclude_call_chains(node: ast.Call, excluded: set[int]) -> None:
|
|
661
704
|
arguments: list[ast.expr] = [*node.args, *(kw.value for kw in node.keywords)]
|
|
662
705
|
excluded.update(id(sub) for arg in arguments for sub in walk(arg))
|
|
663
706
|
|
|
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
RuleExample,
|
|
17
17
|
Severity,
|
|
18
18
|
)
|
|
19
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _flatten_union
|
|
19
20
|
from sarj_python_lint.rules._ast_index import nodes, walk as walk_ast
|
|
20
21
|
|
|
21
22
|
|
|
@@ -349,12 +350,6 @@ def _exception_type_references(expression: ast.expr, imports: ImportIndex) -> fr
|
|
|
349
350
|
return frozenset(reference for reference in references if reference is not None)
|
|
350
351
|
|
|
351
352
|
|
|
352
|
-
def _flatten_union(expression: ast.expr) -> list[ast.expr]:
|
|
353
|
-
if isinstance(expression, ast.BinOp) and isinstance(expression.op, ast.BitOr):
|
|
354
|
-
return [*_flatten_union(expression.left), *_flatten_union(expression.right)]
|
|
355
|
-
return [expression]
|
|
356
|
-
|
|
357
|
-
|
|
358
353
|
def _exception_type_reference(expression: ast.expr, imports: ImportIndex) -> str | None:
|
|
359
354
|
if (
|
|
360
355
|
isinstance(expression, ast.Name)
|
|
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleExample,
|
|
16
16
|
Severity,
|
|
17
17
|
)
|
|
18
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _flatten_union
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
@@ -703,12 +704,6 @@ def _type_references(
|
|
|
703
704
|
return frozenset(reference for reference in references if reference is not None)
|
|
704
705
|
|
|
705
706
|
|
|
706
|
-
def _flatten_union(expression: ast.expr) -> list[ast.expr]:
|
|
707
|
-
if isinstance(expression, ast.BinOp) and isinstance(expression.op, ast.BitOr):
|
|
708
|
-
return [*_flatten_union(expression.left), *_flatten_union(expression.right)]
|
|
709
|
-
return [expression]
|
|
710
|
-
|
|
711
|
-
|
|
712
707
|
def _type_reference(
|
|
713
708
|
expression: ast.expr,
|
|
714
709
|
imports: ImportIndex,
|
|
@@ -20,6 +20,7 @@ from sarj_python_lint.rule_base import (
|
|
|
20
20
|
Severity,
|
|
21
21
|
is_suppressed,
|
|
22
22
|
)
|
|
23
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _flatten_union
|
|
23
24
|
from sarj_python_lint.rules._ast_index import nodes, parent_map, walk as walk_ast
|
|
24
25
|
from sarj_python_lint.rules._inherited_contract import inherited_contract
|
|
25
26
|
from sarj_python_lint.rules._nominal_project import NominalSource
|
|
@@ -805,18 +806,6 @@ def _union_carrier(members: list[ast.expr], imports: ImportIndex, facts: _TypeFa
|
|
|
805
806
|
return _Carrier(carriers[0].shape, raw=any(carrier.raw for carrier in carriers))
|
|
806
807
|
|
|
807
808
|
|
|
808
|
-
def _flatten_union(annotation: ast.expr) -> list[ast.expr]:
|
|
809
|
-
members: list[ast.expr] = []
|
|
810
|
-
pending = [annotation]
|
|
811
|
-
while pending:
|
|
812
|
-
member = pending.pop()
|
|
813
|
-
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
814
|
-
pending.extend((member.right, member.left))
|
|
815
|
-
else:
|
|
816
|
-
members.append(member)
|
|
817
|
-
return members
|
|
818
|
-
|
|
819
|
-
|
|
820
809
|
def _is_none(annotation: ast.expr) -> bool:
|
|
821
810
|
return (isinstance(annotation, ast.Constant) and annotation.value is None) or (
|
|
822
811
|
isinstance(annotation, ast.Name) and annotation.id == "None"
|
|
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleExample,
|
|
16
16
|
Severity,
|
|
17
17
|
)
|
|
18
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members
|
|
18
19
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
19
20
|
from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
|
|
20
21
|
|
|
@@ -291,9 +292,10 @@ def _is_nullable_list(annotation: ast.expr, imports: ImportIndex) -> bool:
|
|
|
291
292
|
|
|
292
293
|
def _union_members(annotation: ast.expr, imports: ImportIndex) -> list[ast.expr] | None:
|
|
293
294
|
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
members: list[ast.expr] = []
|
|
296
|
+
for member in bit_or_members(annotation):
|
|
297
|
+
members.extend(_union_members(member, imports) or [member])
|
|
298
|
+
return members
|
|
297
299
|
if not isinstance(annotation, ast.Subscript):
|
|
298
300
|
return None
|
|
299
301
|
if imports.resolves(annotation.value, sources=_TYPING_SOURCES, symbol="Optional"):
|
{sarj_python_lint-1.66.1 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_str_enum.py
RENAMED
|
@@ -19,6 +19,7 @@ from sarj_python_lint.rule_base import (
|
|
|
19
19
|
Severity,
|
|
20
20
|
is_suppressed,
|
|
21
21
|
)
|
|
22
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _flatten_annotation_union
|
|
22
23
|
from sarj_python_lint.rules._ast_index import children, walk, walk as walk_ast
|
|
23
24
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
24
25
|
from sarj_python_lint.rules._paths import is_test_path
|
|
@@ -1408,18 +1409,6 @@ def _literal_string_values(node: ast.expr) -> list[str] | None:
|
|
|
1408
1409
|
return [value for elt in elts if (value := _str_const(elt)) is not None]
|
|
1409
1410
|
|
|
1410
1411
|
|
|
1411
|
-
def _flatten_annotation_union(node: ast.expr) -> list[ast.expr]:
|
|
1412
|
-
members: list[ast.expr] = []
|
|
1413
|
-
pending = [node]
|
|
1414
|
-
while pending:
|
|
1415
|
-
current = pending.pop()
|
|
1416
|
-
if isinstance(current, ast.BinOp) and isinstance(current.op, ast.BitOr):
|
|
1417
|
-
pending.extend((current.right, current.left))
|
|
1418
|
-
else:
|
|
1419
|
-
members.append(current)
|
|
1420
|
-
return members
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
1412
|
def _is_file_mode_key(key: str) -> bool:
|
|
1424
1413
|
segment = key.rsplit(".", 1)[-1].lstrip("_").lower()
|
|
1425
1414
|
return segment in _FILE_MODE_KEYS or segment.endswith("_mode")
|
|
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
|
|
|
14
14
|
RuleExample,
|
|
15
15
|
Severity,
|
|
16
16
|
)
|
|
17
|
+
from sarj_python_lint.rules._annotation_semantics import bit_or_members as _union_members
|
|
17
18
|
from sarj_python_lint.rules._ast_index import children, walk as walk_ast
|
|
18
19
|
from sarj_python_lint.rules._ast_names import terminal_name as _dotted_tail
|
|
19
20
|
from sarj_python_lint.rules._paths import is_test_path
|
|
@@ -474,12 +475,6 @@ def _optional_member(annotation: ast.expr) -> ast.expr | None:
|
|
|
474
475
|
return None
|
|
475
476
|
|
|
476
477
|
|
|
477
|
-
def _union_members(annotation: ast.expr) -> list[ast.expr]:
|
|
478
|
-
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
479
|
-
return [*_union_members(annotation.left), *_union_members(annotation.right)]
|
|
480
|
-
return [annotation]
|
|
481
|
-
|
|
482
|
-
|
|
483
478
|
def _only_non_none(members: list[ast.expr]) -> ast.expr | None:
|
|
484
479
|
concrete = [member for member in members if not (isinstance(member, ast.Constant) and member.value is None)]
|
|
485
480
|
return concrete[0] if len(concrete) == 1 and len(concrete) < len(members) else None
|