sarj-python-lint 1.66.0__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.0 → sarj_python_lint-1.66.2}/PKG-INFO +1 -1
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/pyproject.toml +1 -1
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_annotation_semantics.py +59 -26
- sarj_python_lint-1.66.2/src/sarj_python_lint/rules/_ast_names.py +63 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_closed_helpers.py +10 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_comments.py +30 -4
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_docstrings.py +19 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_imports.py +16 -1
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_resource_provenance.py +30 -13
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +2 -23
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +2 -21
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +1 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_any_mapping_types.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_async_function_cache.py +1 -11
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +2 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_generic_single_export_module.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -9
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_loguru_exc_info.py +1 -11
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_partial_type_is.py +2 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +1 -7
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_select_star.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_typed_doc_sections.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/over_mocked_test.py +1 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_class_row.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +2 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +6 -24
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +87 -35
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +2 -4
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +3 -10
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_module_level_constant.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +2 -4
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +5 -3
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_str_enum.py +1 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +2 -11
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/production_derived_test_cases.py +1 -15
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +2 -14
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -9
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +2 -20
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_precise_factory_signature.py +2 -17
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +10 -16
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +1 -6
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/restated_test_docstring.py +11 -25
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +1 -10
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/typed_error_reasons.py +1 -8
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +1 -15
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/unused_mock_setup.py +1 -12
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/.gitignore +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/LICENSE +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/README.md +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.66.0 → 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.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.66.0 → 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]:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def root_name(node: ast.expr) -> str | None:
|
|
7
|
+
while isinstance(node, ast.Attribute):
|
|
8
|
+
node = node.value
|
|
9
|
+
return node.id if isinstance(node, ast.Name) else None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def expression_root(node: ast.expr) -> str | None:
|
|
13
|
+
while isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
14
|
+
node = node.value
|
|
15
|
+
return node.id if isinstance(node, ast.Name) else None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def terminal_name(node: ast.expr) -> str | None:
|
|
19
|
+
match node:
|
|
20
|
+
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
21
|
+
return name
|
|
22
|
+
case _:
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def unqualified_name(node: ast.expr) -> str:
|
|
27
|
+
match node:
|
|
28
|
+
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
29
|
+
return name
|
|
30
|
+
case _:
|
|
31
|
+
return ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def dotted_parts(node: ast.expr) -> tuple[str, ...] | None:
|
|
35
|
+
parts: list[str] = []
|
|
36
|
+
current = node
|
|
37
|
+
while isinstance(current, ast.Attribute):
|
|
38
|
+
parts.append(current.attr)
|
|
39
|
+
current = current.value
|
|
40
|
+
if not isinstance(current, ast.Name):
|
|
41
|
+
return None
|
|
42
|
+
parts.append(current.id)
|
|
43
|
+
return tuple(reversed(parts))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def dotted_name(node: ast.expr) -> str | None:
|
|
47
|
+
parts = dotted_parts(node)
|
|
48
|
+
return ".".join(parts) if parts is not None else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def assignment_names(node: ast.AST) -> set[str]:
|
|
52
|
+
match node:
|
|
53
|
+
case ast.Name(id=name):
|
|
54
|
+
return {name}
|
|
55
|
+
case ast.Tuple() | ast.List():
|
|
56
|
+
names: set[str] = set()
|
|
57
|
+
for element in node.elts:
|
|
58
|
+
names.update(assignment_names(element))
|
|
59
|
+
return names
|
|
60
|
+
case ast.Starred(value=value):
|
|
61
|
+
return assignment_names(value)
|
|
62
|
+
case _:
|
|
63
|
+
return set()
|
{sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
@@ -183,3 +183,13 @@ def _plain_decorator(
|
|
|
183
183
|
and not (keyword.arg == "mode" and isinstance(keyword.value, ast.Constant) and keyword.value.value == "wrap")
|
|
184
184
|
for keyword in decorator.keywords
|
|
185
185
|
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def closed_owner(function: ast.FunctionDef | ast.AsyncFunctionDef, context: PythonFileContext) -> bool:
|
|
189
|
+
if not isinstance(function, ast.FunctionDef) or not function.name.startswith("_"):
|
|
190
|
+
return False
|
|
191
|
+
references = closed_references(context, function, context.resource_provenance)
|
|
192
|
+
return bool(references) and all(
|
|
193
|
+
(call := direct_call(reference, context)) is not None and call_matches(function, call)
|
|
194
|
+
for reference in references
|
|
195
|
+
)
|
|
@@ -326,7 +326,13 @@ class _Scan(NamedTuple):
|
|
|
326
326
|
ordered: _Ordered
|
|
327
327
|
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
class _ScanMemo(NamedTuple):
|
|
330
|
+
source: str
|
|
331
|
+
scan: _Scan | None
|
|
332
|
+
comment_lines: frozenset[int] | None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
_last_scan: _ScanMemo | None = None
|
|
330
336
|
|
|
331
337
|
|
|
332
338
|
def all_comments(source: str) -> _CommentScan[_Ordered]:
|
|
@@ -334,15 +340,35 @@ def all_comments(source: str) -> _CommentScan[_Ordered]:
|
|
|
334
340
|
return _CommentScan(scan.ordered, scan.first_code_line)
|
|
335
341
|
|
|
336
342
|
|
|
343
|
+
def comment_line_numbers(source: str) -> frozenset[int]:
|
|
344
|
+
global _last_scan # ruff: ignore[global-statement] -- retain the existing single-source memo for both comment projections.
|
|
345
|
+
cached = _last_scan
|
|
346
|
+
scan = cached.scan if cached is not None and cached.source is source else None
|
|
347
|
+
if cached is not None and cached.source is source and cached.comment_lines is not None:
|
|
348
|
+
return cached.comment_lines
|
|
349
|
+
lines: frozenset[int]
|
|
350
|
+
if scan is not None:
|
|
351
|
+
lines = frozenset(comment.line for comment in scan.ordered)
|
|
352
|
+
else:
|
|
353
|
+
try:
|
|
354
|
+
tokens = tokenize.generate_tokens(io.StringIO(source).readline)
|
|
355
|
+
lines = frozenset(token.start[0] for token in tokens if token.type == tokenize.COMMENT)
|
|
356
|
+
except IndentationError, tokenize.TokenError:
|
|
357
|
+
lines = frozenset()
|
|
358
|
+
_last_scan = _ScanMemo(source, scan, lines)
|
|
359
|
+
return lines
|
|
360
|
+
|
|
361
|
+
|
|
337
362
|
def _scan_memo(source: str) -> _Scan:
|
|
338
363
|
global _last_scan # ruff: ignore[global-statement] — single-slot memo; the CLI runs rules per file sequentially
|
|
339
|
-
if _last_scan is not None and _last_scan
|
|
340
|
-
|
|
364
|
+
cached = _last_scan if _last_scan is not None and _last_scan.source is source else None
|
|
365
|
+
if cached is not None and cached.scan is not None:
|
|
366
|
+
return cached.scan
|
|
341
367
|
try:
|
|
342
368
|
result = _scan(source)
|
|
343
369
|
except UnicodeError:
|
|
344
370
|
result = _Scan([], [], set(), 1 << 30, [])
|
|
345
|
-
_last_scan = (source, result)
|
|
371
|
+
_last_scan = _ScanMemo(source, result, cached.comment_lines if cached is not None else None)
|
|
346
372
|
return result
|
|
347
373
|
|
|
348
374
|
|
{sarj_python_lint-1.66.0 → sarj_python_lint-1.66.2}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
@@ -13,9 +13,16 @@ if TYPE_CHECKING:
|
|
|
13
13
|
from collections.abc import Iterable
|
|
14
14
|
|
|
15
15
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
16
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
_GETATTR_MIN_ARGS = 2
|
|
20
|
+
_RUNTIME_DOC_DECORATORS = (
|
|
21
|
+
(frozenset({"agents"}), "function_tool"),
|
|
22
|
+
(frozenset({"click"}), "command"),
|
|
23
|
+
(frozenset({"typer"}), "command"),
|
|
24
|
+
)
|
|
25
|
+
_OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
|
|
19
26
|
|
|
20
27
|
# Docstring filler that says nothing about *which* thing is being described.
|
|
21
28
|
_BASE_STOPWORDS = frozenset(
|
|
@@ -259,6 +266,18 @@ def decorator_markers(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDe
|
|
|
259
266
|
return markers
|
|
260
267
|
|
|
261
268
|
|
|
269
|
+
def is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
|
|
270
|
+
if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
|
|
271
|
+
return True
|
|
272
|
+
for decorator in node.decorator_list:
|
|
273
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
274
|
+
if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
|
|
275
|
+
return True
|
|
276
|
+
if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
|
|
277
|
+
return True
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
|
|
262
281
|
def docstring_expression(
|
|
263
282
|
node: ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef,
|
|
264
283
|
) -> ast.Expr | None:
|
|
@@ -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)
|
|
@@ -17,6 +17,7 @@ from sarj_python_lint.rule_base import (
|
|
|
17
17
|
Severity,
|
|
18
18
|
)
|
|
19
19
|
from sarj_python_lint.rules._ast_index import nodes, walk
|
|
20
|
+
from sarj_python_lint.rules._ast_names import unqualified_name as _call_name
|
|
20
21
|
from sarj_python_lint.rules._paths import is_test_path
|
|
21
22
|
from sarj_python_lint.rules._sql import is_store_module, sql_string_value, strip_sql_noise
|
|
22
23
|
from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import analytical_signal
|
|
@@ -148,14 +149,6 @@ def _query_argument(call: ast.Call) -> ast.expr | None:
|
|
|
148
149
|
return next((keyword.value for keyword in call.keywords if keyword.arg in _QUERY_KEYWORDS), None)
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
def _call_name(function: ast.expr) -> str:
|
|
152
|
-
match function:
|
|
153
|
-
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
154
|
-
return name
|
|
155
|
-
case _:
|
|
156
|
-
return ""
|
|
157
|
-
|
|
158
|
-
|
|
159
152
|
def _postgres_sql_constructors(
|
|
160
153
|
tree: ast.Module, parents: Mapping[ast.AST, ast.AST], *, node_index: NodeIndex | None = None
|
|
161
154
|
) -> frozenset[_ConstructorImport]:
|
|
@@ -20,13 +20,12 @@ from sarj_python_lint.rule_base import (
|
|
|
20
20
|
from sarj_python_lint.rules._ast_index import children
|
|
21
21
|
from sarj_python_lint.rules._comments import is_protected, stem
|
|
22
22
|
from sarj_python_lint.rules._docstrings import (
|
|
23
|
-
PROMPT_DECORATOR_MARKERS,
|
|
24
23
|
VALUE_MARKER_RE,
|
|
25
24
|
annotation_tokens,
|
|
26
25
|
arg_entries,
|
|
27
26
|
arg_section,
|
|
28
|
-
decorator_markers,
|
|
29
27
|
identifier_stems,
|
|
28
|
+
is_runtime_consumed_or_overload,
|
|
30
29
|
restates,
|
|
31
30
|
)
|
|
32
31
|
|
|
@@ -40,14 +39,6 @@ if TYPE_CHECKING:
|
|
|
40
39
|
|
|
41
40
|
_ARGUMENT_CONSTRAINT_RE = re.compile(r"\b(available|current|existing|optional|required|supported)\b", re.IGNORECASE)
|
|
42
41
|
|
|
43
|
-
_RUNTIME_DOC_DECORATORS = (
|
|
44
|
-
(frozenset({"agents"}), "function_tool"),
|
|
45
|
-
(frozenset({"click"}), "command"),
|
|
46
|
-
(frozenset({"typer"}), "command"),
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
_OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
|
|
50
|
-
|
|
51
42
|
|
|
52
43
|
@dataclass(frozen=True, slots=True)
|
|
53
44
|
class _ParameterFacts:
|
|
@@ -151,7 +142,7 @@ class DocstringArgsRestateSignature(Rule):
|
|
|
151
142
|
block = arg_section(docstring)
|
|
152
143
|
if block is None or VALUE_MARKER_RE.search(block) or is_protected(block):
|
|
153
144
|
return
|
|
154
|
-
if
|
|
145
|
+
if is_runtime_consumed_or_overload(node, imports):
|
|
155
146
|
return
|
|
156
147
|
entries = arg_entries(block)
|
|
157
148
|
if not entries:
|
|
@@ -202,15 +193,3 @@ def _parameter_stems(
|
|
|
202
193
|
known = callable_stems | identifier_stems(argument.arg) | annotation_stems
|
|
203
194
|
parameters[argument.arg] = _ParameterFacts(frozenset(known), frozenset(annotation_stems))
|
|
204
195
|
return parameters
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def _is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
|
|
208
|
-
if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
|
|
209
|
-
return True
|
|
210
|
-
for decorator in node.decorator_list:
|
|
211
|
-
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
212
|
-
if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
|
|
213
|
-
return True
|
|
214
|
-
if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
|
|
215
|
-
return True
|
|
216
|
-
return False
|
|
@@ -20,11 +20,10 @@ from sarj_python_lint.rule_base import (
|
|
|
20
20
|
from sarj_python_lint.rules._ast_index import children
|
|
21
21
|
from sarj_python_lint.rules._comments import is_protected, stem
|
|
22
22
|
from sarj_python_lint.rules._docstrings import (
|
|
23
|
-
PROMPT_DECORATOR_MARKERS,
|
|
24
23
|
VALUE_MARKER_RE,
|
|
25
24
|
annotation_tokens,
|
|
26
|
-
decorator_markers,
|
|
27
25
|
identifier_stems,
|
|
26
|
+
is_runtime_consumed_or_overload,
|
|
28
27
|
restates,
|
|
29
28
|
sections,
|
|
30
29
|
)
|
|
@@ -51,12 +50,6 @@ _RESULT_SEMANTIC_RE = re.compile(
|
|
|
51
50
|
re.IGNORECASE,
|
|
52
51
|
)
|
|
53
52
|
|
|
54
|
-
_RUNTIME_DOC_DECORATORS = (
|
|
55
|
-
(frozenset({"agents"}), "function_tool"),
|
|
56
|
-
(frozenset({"click"}), "command"),
|
|
57
|
-
(frozenset({"typer"}), "command"),
|
|
58
|
-
)
|
|
59
|
-
_OVERLOAD_MODULES = frozenset({"typing", "typing_extensions"})
|
|
60
53
|
_GENERATOR_TYPES = frozenset({"AsyncGenerator", "AsyncIterable", "AsyncIterator", "Generator", "Iterable", "Iterator"})
|
|
61
54
|
_BROAD_RETURN_TYPES = frozenset({"Any", "object"})
|
|
62
55
|
|
|
@@ -228,7 +221,7 @@ class DocstringReturnsRestateSignature(Rule):
|
|
|
228
221
|
or _TYPED_RESULT_RE.search(block)
|
|
229
222
|
):
|
|
230
223
|
return
|
|
231
|
-
if
|
|
224
|
+
if is_runtime_consumed_or_overload(node, imports):
|
|
232
225
|
return
|
|
233
226
|
annotation = _result_annotation(node, section.name)
|
|
234
227
|
if annotation is None or _is_broad_annotation(annotation):
|
|
@@ -313,15 +306,3 @@ def _contains_yield(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
|
313
306
|
|
|
314
307
|
def _is_broad_annotation(annotation: ast.expr) -> bool:
|
|
315
308
|
return _annotation_name(annotation) in _BROAD_RETURN_TYPES
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def _is_runtime_consumed_or_overload(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> bool:
|
|
319
|
-
if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
|
|
320
|
-
return True
|
|
321
|
-
for decorator in node.decorator_list:
|
|
322
|
-
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
323
|
-
if imports.resolves(target, sources=_OVERLOAD_MODULES, symbol="overload"):
|
|
324
|
-
return True
|
|
325
|
-
if any(imports.resolves(target, sources=sources, symbol=symbol) for sources, symbol in _RUNTIME_DOC_DECORATORS):
|
|
326
|
-
return True
|
|
327
|
-
return False
|
|
@@ -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:
|
|
@@ -21,6 +21,7 @@ from sarj_python_lint.rule_base import (
|
|
|
21
21
|
)
|
|
22
22
|
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
|
|
23
23
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
24
|
+
from sarj_python_lint.rules._ast_names import expression_root as _expression_root
|
|
24
25
|
from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
|
|
25
26
|
|
|
26
27
|
|
|
@@ -568,12 +569,6 @@ def _type_attribute_mutation(call: ast.Call, imports: ImportIndex, sensitive: se
|
|
|
568
569
|
return known and _expression_root(call.args[0]) in sensitive
|
|
569
570
|
|
|
570
571
|
|
|
571
|
-
def _expression_root(node: ast.expr) -> str | None:
|
|
572
|
-
while isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
573
|
-
node = node.value
|
|
574
|
-
return node.id if isinstance(node, ast.Name) else None
|
|
575
|
-
|
|
576
|
-
|
|
577
572
|
def _unshadowed_type_expression(
|
|
578
573
|
node: ast.expr, parents: Mapping[ast.AST, ast.AST], scope_cache: dict[int, frozenset[str]]
|
|
579
574
|
) -> bool:
|
|
@@ -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
|
|
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
Severity,
|
|
17
17
|
is_suppressed,
|
|
18
18
|
)
|
|
19
|
+
from sarj_python_lint.rules._closed_helpers import executable_body
|
|
19
20
|
from sarj_python_lint.rules._imports import TYPING_SOURCES
|
|
20
21
|
from sarj_python_lint.rules._type_guard_symbols import (
|
|
21
22
|
builtin_guard_symbol,
|
|
@@ -189,14 +190,7 @@ def _sole_parameter(function: ast.FunctionDef) -> ast.arg | None:
|
|
|
189
190
|
|
|
190
191
|
|
|
191
192
|
def _single_return(function: ast.FunctionDef) -> ast.Return | None:
|
|
192
|
-
body = function
|
|
193
|
-
if (
|
|
194
|
-
body
|
|
195
|
-
and isinstance(body[0], ast.Expr)
|
|
196
|
-
and isinstance(body[0].value, ast.Constant)
|
|
197
|
-
and isinstance(body[0].value.value, str)
|
|
198
|
-
):
|
|
199
|
-
body = body[1:]
|
|
193
|
+
body = executable_body(function)
|
|
200
194
|
return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
|
|
201
195
|
|
|
202
196
|
|
|
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleExample,
|
|
16
16
|
)
|
|
17
17
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
18
|
+
from sarj_python_lint.rules._ast_names import unqualified_name as _annotation_name
|
|
18
19
|
from sarj_python_lint.rules._exports import mentions_dunder_all
|
|
19
20
|
|
|
20
21
|
|
|
@@ -164,14 +165,6 @@ def _dunder_all_matches_primary(tree: ast.Module, primary_name: str) -> bool:
|
|
|
164
165
|
return isinstance(entry, ast.Constant) and entry.value == primary_name
|
|
165
166
|
|
|
166
167
|
|
|
167
|
-
def _annotation_name(node: ast.expr) -> str:
|
|
168
|
-
match node:
|
|
169
|
-
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
170
|
-
return name
|
|
171
|
-
case _:
|
|
172
|
-
return ""
|
|
173
|
-
|
|
174
|
-
|
|
175
168
|
def _is_skipped_path(path: Path) -> bool:
|
|
176
169
|
return "tests" in path.parts
|
|
177
170
|
|