sarj-python-lint 1.63.1__tar.gz → 1.66.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/.gitignore +3 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__main__.py +22 -25
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_file_context.py +19 -0
- sarj_python_lint-1.66.0/src/sarj_python_lint/cast_access.py +252 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rule_base.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_annotation_semantics.py +4 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_copy_on_write.py +2 -4
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +1 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_registry.py +2 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resource_provenance.py +39 -28
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_sql.py +25 -15
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_store_writes.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +1 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +36 -30
- sarj_python_lint-1.66.0/src/sarj_python_lint/rules/no_blanket_suppression.py +134 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +3 -7
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +1 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +32 -33
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_select_star.py +73 -30
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +42 -12
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +1 -1
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +1 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +3 -3
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +1 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +4 -6
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_mock_setup.py +2 -2
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +2 -2
- sarj_python_lint-1.66.0/src/sarj_python_lint/suppression_policy.py +5 -0
- sarj_python_lint-1.63.1/src/sarj_python_lint/cast_access.py +0 -86
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/LICENSE +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/README.md +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
|
@@ -44,6 +44,9 @@ Thumbs.db
|
|
|
44
44
|
!.claude/skills/test-value/
|
|
45
45
|
.claude/skills/test-value/*
|
|
46
46
|
!.claude/skills/test-value/SKILL.md
|
|
47
|
+
!.claude/skills/100-passes/
|
|
48
|
+
.claude/skills/100-passes/*
|
|
49
|
+
!.claude/skills/100-passes/SKILL.md
|
|
47
50
|
apps/docs/.wrangler/
|
|
48
51
|
apps/docs/.dev.vars*
|
|
49
52
|
!apps/docs/.dev.vars.example
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.66.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -23,6 +23,7 @@ from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity,
|
|
|
23
23
|
from sarj_python_lint.rules import REGISTRY
|
|
24
24
|
from sarj_python_lint.rules._paths import clear_path_caches
|
|
25
25
|
from sarj_python_lint.rules._project_index import ProjectIndexSet
|
|
26
|
+
from sarj_python_lint.suppression_policy import PROTECTED_SUPPRESSION_CODES
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
SKIP_DIR_NAMES = frozenset(
|
|
@@ -46,11 +47,17 @@ SKIP_DIR_NAMES = frozenset(
|
|
|
46
47
|
}
|
|
47
48
|
)
|
|
48
49
|
|
|
49
|
-
#
|
|
50
|
-
# hand-written source worth linting.
|
|
50
|
+
# Bound resource use, but never turn an unanalyzed selected file into success.
|
|
51
51
|
_MAX_FILE_BYTES = 500_000
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _checked_size(path: Path) -> Path:
|
|
55
|
+
if path.stat().st_size > _MAX_FILE_BYTES:
|
|
56
|
+
msg = f"selected Python source exceeds {_MAX_FILE_BYTES} bytes: {path}; use an explicit path exclusion for generated source"
|
|
57
|
+
raise ValueError(msg)
|
|
58
|
+
return path
|
|
59
|
+
|
|
60
|
+
|
|
54
61
|
def expand_paths(paths: list[Path]) -> list[Path]:
|
|
55
62
|
out: list[Path] = []
|
|
56
63
|
for p in paths:
|
|
@@ -58,11 +65,7 @@ def expand_paths(paths: list[Path]) -> list[Path]:
|
|
|
58
65
|
msg = f"input does not exist: {p}"
|
|
59
66
|
raise ValueError(msg)
|
|
60
67
|
if p.is_file():
|
|
61
|
-
|
|
62
|
-
if p.stat().st_size <= _MAX_FILE_BYTES:
|
|
63
|
-
out.append(p)
|
|
64
|
-
except OSError:
|
|
65
|
-
pass
|
|
68
|
+
out.append(_checked_size(p))
|
|
66
69
|
continue
|
|
67
70
|
out.extend(_python_files(p))
|
|
68
71
|
return out
|
|
@@ -78,11 +81,8 @@ def _python_files(p: Path) -> list[Path]:
|
|
|
78
81
|
if not fnmatch(name, "*.py"):
|
|
79
82
|
continue
|
|
80
83
|
child = directory / name
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
out.append(child)
|
|
84
|
-
except OSError:
|
|
85
|
-
continue
|
|
84
|
+
if child.is_file():
|
|
85
|
+
out.append(_checked_size(child))
|
|
86
86
|
return sorted(out)
|
|
87
87
|
|
|
88
88
|
|
|
@@ -109,12 +109,7 @@ def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
|
|
|
109
109
|
rule.prepare_session(session)
|
|
110
110
|
clear_path_caches()
|
|
111
111
|
expanded = expand_paths(paths)
|
|
112
|
-
loaded:
|
|
113
|
-
for path in expanded:
|
|
114
|
-
try:
|
|
115
|
-
loaded[path] = read_python_source(path)
|
|
116
|
-
except OSError:
|
|
117
|
-
continue
|
|
112
|
+
loaded = {path: read_python_source(path) for path in expanded}
|
|
118
113
|
project_rules = [rule for rule in rules if isinstance(rule, ProjectRule)]
|
|
119
114
|
if project_rules:
|
|
120
115
|
indexes = ProjectIndexSet.build(expanded, loaded, facts=session.first_party)
|
|
@@ -131,14 +126,13 @@ def check_source(
|
|
|
131
126
|
rules: list[Rule], p: Path, source: str, *, session: AnalysisSession | None = None
|
|
132
127
|
) -> list[Diagnostic]:
|
|
133
128
|
context = PythonFileContext(p, source, session)
|
|
129
|
+
if rules and context.tree is None:
|
|
130
|
+
msg = f"selected Python source could not be parsed: {p}"
|
|
131
|
+
raise ValueError(msg)
|
|
134
132
|
source_lines = context.source_lines
|
|
135
133
|
raw = [diagnostic for rule in rules for diagnostic in rule.check_context(context)]
|
|
136
134
|
return deduplicate_diagnostics(
|
|
137
|
-
[
|
|
138
|
-
diagnostic
|
|
139
|
-
for diagnostic in raw
|
|
140
|
-
if diagnostic.code == "SARJ419" or not is_suppressed(source_lines, diagnostic.line, diagnostic.code)
|
|
141
|
-
],
|
|
135
|
+
[diagnostic for diagnostic in raw if not is_suppressed(source_lines, diagnostic.line, diagnostic.code)],
|
|
142
136
|
source=source,
|
|
143
137
|
context=context,
|
|
144
138
|
)
|
|
@@ -157,6 +151,9 @@ def analyze(
|
|
|
157
151
|
|
|
158
152
|
_DIAGNOSTIC_PRECEDENCE = MappingProxyType(
|
|
159
153
|
{
|
|
154
|
+
"SARJ054": frozenset({"SARJ419"}),
|
|
155
|
+
"SARJ448": frozenset({"SARJ419"}),
|
|
156
|
+
"SARJ523": frozenset({"SARJ419"}),
|
|
160
157
|
"SARJ050": frozenset({"SARJ420"}),
|
|
161
158
|
"SARJ084": frozenset({"SARJ050", "SARJ420"}),
|
|
162
159
|
"SARJ085": frozenset({"SARJ420"}),
|
|
@@ -417,7 +414,7 @@ def _explain(wanted: str) -> int:
|
|
|
417
414
|
def _baseline_counts(diags: list[Diagnostic]) -> dict[str, dict[str, int]]:
|
|
418
415
|
counts: dict[str, dict[str, int]] = {}
|
|
419
416
|
for d in diags:
|
|
420
|
-
if d.severity is Severity.WARNING:
|
|
417
|
+
if d.severity is Severity.WARNING or d.code in PROTECTED_SUPPRESSION_CODES:
|
|
421
418
|
continue
|
|
422
419
|
key = _baseline_path(d.path)
|
|
423
420
|
counts.setdefault(key, {})
|
|
@@ -467,7 +464,7 @@ def _apply_baseline(
|
|
|
467
464
|
seen: Counter[tuple[str, str]] = Counter()
|
|
468
465
|
out: list[Diagnostic] = []
|
|
469
466
|
for d in diags:
|
|
470
|
-
if d.severity is Severity.WARNING:
|
|
467
|
+
if d.severity is Severity.WARNING or d.code in PROTECTED_SUPPRESSION_CODES:
|
|
471
468
|
out.append(d)
|
|
472
469
|
continue
|
|
473
470
|
path_key = _baseline_path(d.path, root=root)
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, final
|
|
|
6
6
|
|
|
7
7
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
8
8
|
from sarj_python_lint._source import symbol_prefilter_source
|
|
9
|
+
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
|
|
9
10
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
10
11
|
from sarj_python_lint.rules._fastapi import FastapiIndex
|
|
11
12
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
@@ -18,6 +19,7 @@ if TYPE_CHECKING:
|
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
|
|
20
21
|
from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis
|
|
22
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
21
23
|
from sarj_python_lint.rules._test_provenance import TestProvenance
|
|
22
24
|
|
|
23
25
|
|
|
@@ -91,6 +93,10 @@ class PythonFileContext:
|
|
|
91
93
|
def module_imports(self) -> ImportIndex:
|
|
92
94
|
return ImportIndex.from_tree(self._valid_tree(), module_scope_only=True)
|
|
93
95
|
|
|
96
|
+
@cached_property
|
|
97
|
+
def annotation_semantics(self) -> AnnotationSemantics:
|
|
98
|
+
return AnnotationSemantics.from_imports(self._valid_tree(), self.module_imports)
|
|
99
|
+
|
|
94
100
|
@cached_property
|
|
95
101
|
def dynamic_namespace(self) -> bool:
|
|
96
102
|
return dynamic_namespace_access(self.nodes(ast.AST), self.imports, self.parents)
|
|
@@ -117,3 +123,16 @@ class PythonFileContext:
|
|
|
117
123
|
from sarj_python_lint.rules._copy_on_write import CopyOnWriteAnalysis # ruff: ignore[import-outside-top-level] — share ownership analysis only when requested
|
|
118
124
|
|
|
119
125
|
return CopyOnWriteAnalysis(self)
|
|
126
|
+
|
|
127
|
+
@cached_property
|
|
128
|
+
def resource_provenance(self) -> ResourceProvenance:
|
|
129
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance # ruff: ignore[import-outside-top-level] -- file-owned binding facts are lazy.
|
|
130
|
+
|
|
131
|
+
# The owner caches this analysis; borrowing prevents a reference cycle.
|
|
132
|
+
return ResourceProvenance(self, borrow_context=True)
|
|
133
|
+
|
|
134
|
+
@cached_property
|
|
135
|
+
def static_resource_provenance(self) -> ResourceProvenance:
|
|
136
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance # ruff: ignore[import-outside-top-level] -- keep static-namespace policy in a separate cached analysis.
|
|
137
|
+
|
|
138
|
+
return ResourceProvenance(self, require_static_namespace=True, borrow_context=True)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
8
|
+
from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterator
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_TYPING = frozenset({"typing", "typing_extensions"})
|
|
19
|
+
_GETATTR_ARITIES = frozenset({2, 3})
|
|
20
|
+
_MAX_LITERAL_DEPTH = 12
|
|
21
|
+
_CAST_LENGTH = len("cast")
|
|
22
|
+
_COMPREHENSIONS = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class CastAccess:
|
|
27
|
+
node: ast.Call | ast.Subscript | ast.ImportFrom
|
|
28
|
+
qualified_api: str
|
|
29
|
+
kind: Literal["wildcard", "reflection"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def indirect_typing_cast_accesses(path: Path, source: str) -> tuple[CastAccess, ...]:
|
|
33
|
+
context = PythonFileContext(path, source)
|
|
34
|
+
if not any(marker in context.symbol_source for marker in ("*", "getattr", "__dict__", "vars", "__getattribute__")):
|
|
35
|
+
return ()
|
|
36
|
+
if context.tree is None:
|
|
37
|
+
return ()
|
|
38
|
+
accesses = [
|
|
39
|
+
CastAccess(node, f"{node.module}.cast", "wildcard")
|
|
40
|
+
for node in context.nodes(ast.ImportFrom)
|
|
41
|
+
if node.level == 0 and node.module in _TYPING and any(alias.name == "*" for alias in node.names)
|
|
42
|
+
]
|
|
43
|
+
accesses.extend(_reflection_accesses(context))
|
|
44
|
+
return tuple(sorted(accesses, key=lambda access: (access.node.lineno, access.node.col_offset)))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _reflection_accesses(context: PythonFileContext) -> list[CastAccess]:
|
|
48
|
+
provenance = context.resource_provenance
|
|
49
|
+
candidates = [
|
|
50
|
+
(node, receiver)
|
|
51
|
+
for node in context.nodes(ast.Call, ast.Subscript)
|
|
52
|
+
if (receiver := _cast_receiver(node, provenance)) is not None
|
|
53
|
+
]
|
|
54
|
+
if not candidates:
|
|
55
|
+
return []
|
|
56
|
+
receivers = {id(receiver) for _, receiver in candidates}
|
|
57
|
+
proven = [
|
|
58
|
+
(node, module)
|
|
59
|
+
for node, receiver in candidates
|
|
60
|
+
if (module := _typing_receiver(receiver, provenance, receivers)) is not None
|
|
61
|
+
]
|
|
62
|
+
safe_reflection = {id(carrier) for node, _ in proven if (carrier := _reflection_carrier(node)) is not None}
|
|
63
|
+
namespace_hazard = dynamic_namespace_access(
|
|
64
|
+
(node for node in context.nodes(ast.AST) if id(node) not in safe_reflection), context.imports, context.parents
|
|
65
|
+
)
|
|
66
|
+
if provenance.wildcard or namespace_hazard or not provenance.builtin_namespace_untouched:
|
|
67
|
+
return []
|
|
68
|
+
return [CastAccess(node, f"{module}.cast", "reflection") for node, module in proven]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _cast_receiver(node: ast.Call | ast.Subscript, provenance: ResourceProvenance) -> ast.Name | None:
|
|
72
|
+
if isinstance(node, ast.Call):
|
|
73
|
+
return _call_receiver(node, provenance)
|
|
74
|
+
if not isinstance(node.ctx, ast.Load) or _static_text(node.slice) != "cast":
|
|
75
|
+
return None
|
|
76
|
+
namespace = node.value
|
|
77
|
+
if isinstance(namespace, ast.Attribute) and namespace.attr == "__dict__" and isinstance(namespace.value, ast.Name):
|
|
78
|
+
return namespace.value
|
|
79
|
+
if isinstance(namespace, ast.Call) and len(namespace.args) == 1 and _builtin_call(namespace, "vars", provenance):
|
|
80
|
+
return namespace.args[0] if isinstance(namespace.args[0], ast.Name) else None
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _call_receiver(node: ast.Call, provenance: ResourceProvenance) -> ast.Name | None:
|
|
85
|
+
if (
|
|
86
|
+
len(node.args) in _GETATTR_ARITIES
|
|
87
|
+
and _builtin_call(node, "getattr", provenance)
|
|
88
|
+
and _static_text(node.args[1]) == "cast"
|
|
89
|
+
):
|
|
90
|
+
return node.args[0] if isinstance(node.args[0], ast.Name) else None
|
|
91
|
+
if (
|
|
92
|
+
isinstance(node.func, ast.Attribute)
|
|
93
|
+
and node.func.attr == "__getattribute__"
|
|
94
|
+
and len(node.args) == 1
|
|
95
|
+
and not node.keywords
|
|
96
|
+
and _static_text(node.args[0]) == "cast"
|
|
97
|
+
):
|
|
98
|
+
return node.func.value if isinstance(node.func.value, ast.Name) else None
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _builtin_call(call: ast.Call, name: str, provenance: ResourceProvenance) -> bool:
|
|
103
|
+
if not isinstance(call.func, ast.Name) or call.func.id != name or call.keywords:
|
|
104
|
+
return False
|
|
105
|
+
return not _type_parameter_shadowed(name, call.func, provenance) and all(
|
|
106
|
+
name not in provenance.bindings.get(scope, {}) for scope in _lexical_scopes(call.func, provenance)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _lexical_scopes(node: ast.AST, provenance: ResourceProvenance) -> Iterator[ast.AST]:
|
|
111
|
+
scope = _evaluation_scope(node, provenance)
|
|
112
|
+
immediate = True
|
|
113
|
+
while scope is not None:
|
|
114
|
+
# Ordinary nested code blocks do not capture class locals.
|
|
115
|
+
if immediate or not isinstance(scope, ast.ClassDef):
|
|
116
|
+
yield scope
|
|
117
|
+
immediate = False
|
|
118
|
+
scope = _evaluation_scope(scope, provenance)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _evaluation_scope(node: ast.AST, provenance: ResourceProvenance) -> ast.AST | None:
|
|
122
|
+
scope = provenance.scope(node)
|
|
123
|
+
if isinstance(scope, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) and _owner_annotation(
|
|
124
|
+
node, scope, provenance
|
|
125
|
+
):
|
|
126
|
+
node, scope = scope, provenance.scope(scope)
|
|
127
|
+
while isinstance(scope, _COMPREHENSIONS):
|
|
128
|
+
iterable = scope.generators[0].iter
|
|
129
|
+
if iterable is not node and not provenance.contains(iterable, node):
|
|
130
|
+
break
|
|
131
|
+
# A comprehension's first iterable is evaluated in the enclosing scope.
|
|
132
|
+
node, scope = scope, provenance.scope(scope)
|
|
133
|
+
return scope
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _owner_annotation(
|
|
137
|
+
node: ast.AST, owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef, provenance: ResourceProvenance
|
|
138
|
+
) -> bool:
|
|
139
|
+
if any(parameter is node or provenance.contains(parameter, node) for parameter in owner.type_params):
|
|
140
|
+
return True
|
|
141
|
+
return isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef) and _function_annotation(node, owner, provenance)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _function_annotation(
|
|
145
|
+
node: ast.AST, function: ast.FunctionDef | ast.AsyncFunctionDef, provenance: ResourceProvenance
|
|
146
|
+
) -> bool:
|
|
147
|
+
current = node
|
|
148
|
+
while (parent := provenance.context.parents.get(current)) is not None:
|
|
149
|
+
if (parent is function and current is function.returns) or (
|
|
150
|
+
isinstance(parent, ast.arg) and current is parent.annotation
|
|
151
|
+
):
|
|
152
|
+
return True
|
|
153
|
+
if parent is function:
|
|
154
|
+
break
|
|
155
|
+
current = parent
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _type_parameter_shadowed(name: str, node: ast.AST, provenance: ResourceProvenance) -> bool:
|
|
160
|
+
current: ast.AST | None = node
|
|
161
|
+
while current is not None:
|
|
162
|
+
if (
|
|
163
|
+
isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias)
|
|
164
|
+
and any(
|
|
165
|
+
isinstance(parameter, ast.TypeVar | ast.TypeVarTuple | ast.ParamSpec) and parameter.name == name
|
|
166
|
+
for parameter in current.type_params
|
|
167
|
+
)
|
|
168
|
+
and not _outside_parameter_scope(node, current, provenance)
|
|
169
|
+
and not _local_binding_overrides(name, node, current, provenance)
|
|
170
|
+
):
|
|
171
|
+
return True
|
|
172
|
+
current = provenance.context.parents.get(current)
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _outside_parameter_scope(
|
|
177
|
+
node: ast.AST,
|
|
178
|
+
owner: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias,
|
|
179
|
+
provenance: ResourceProvenance,
|
|
180
|
+
) -> bool:
|
|
181
|
+
outside = [] if isinstance(owner, ast.TypeAlias) else list(owner.decorator_list)
|
|
182
|
+
if isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
183
|
+
outside.extend(owner.args.defaults)
|
|
184
|
+
outside.extend(value for value in owner.args.kw_defaults if value is not None)
|
|
185
|
+
# Defaults and decorators evaluate in the enclosing scope.
|
|
186
|
+
return any(value is node or provenance.contains(value, node) for value in outside)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _local_binding_overrides(name: str, node: ast.AST, owner: ast.AST, provenance: ResourceProvenance) -> bool:
|
|
190
|
+
for scope in _lexical_scopes(node, provenance):
|
|
191
|
+
if scope is not owner and not provenance.contains(owner, scope):
|
|
192
|
+
return False
|
|
193
|
+
if name in provenance.bindings.get(scope, {}):
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _static_text(node: ast.expr, depth: int = 0) -> str | None:
|
|
199
|
+
# Evaluate only bounded, side-effect-free string syntax, never runtime code.
|
|
200
|
+
if depth > _MAX_LITERAL_DEPTH:
|
|
201
|
+
return None
|
|
202
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
203
|
+
return node.value if len(node.value) <= _CAST_LENGTH else None
|
|
204
|
+
if isinstance(node, ast.JoinedStr):
|
|
205
|
+
# Parsing adjacent static f-strings folds them into one Constant.
|
|
206
|
+
return _static_text(node.values[0], depth + 1) if len(node.values) == 1 else None
|
|
207
|
+
if not isinstance(node, ast.BinOp) or not isinstance(node.op, ast.Add):
|
|
208
|
+
return None
|
|
209
|
+
left, right = _static_text(node.left, depth + 1), _static_text(node.right, depth + 1)
|
|
210
|
+
if left is None or right is None:
|
|
211
|
+
return None
|
|
212
|
+
text = left + right
|
|
213
|
+
return text if len(text) <= _CAST_LENGTH else None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _reflection_carrier(node: ast.Call | ast.Subscript) -> ast.AST | None:
|
|
217
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
|
218
|
+
return node.func
|
|
219
|
+
if isinstance(node, ast.Subscript):
|
|
220
|
+
return node.value.func if isinstance(node.value, ast.Call) else node.value
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _typing_receiver(node: ast.Name, provenance: ResourceProvenance, receivers: set[int]) -> str | None:
|
|
225
|
+
if node.id in provenance.mutated | provenance.external_bindings:
|
|
226
|
+
return None
|
|
227
|
+
binding = _name_binding(node, provenance)
|
|
228
|
+
if not (
|
|
229
|
+
isinstance(binding, ast.alias)
|
|
230
|
+
and isinstance(provenance.context.parents.get(binding), ast.Import)
|
|
231
|
+
and binding.name in _TYPING
|
|
232
|
+
):
|
|
233
|
+
return None
|
|
234
|
+
for read in provenance.reads.get(node.id, ()):
|
|
235
|
+
if _name_binding(read, provenance) is not binding:
|
|
236
|
+
continue
|
|
237
|
+
parent = provenance.context.parents.get(read)
|
|
238
|
+
if isinstance(parent, ast.Attribute) and parent.value is read:
|
|
239
|
+
continue
|
|
240
|
+
if id(read) in receivers:
|
|
241
|
+
continue
|
|
242
|
+
return None
|
|
243
|
+
return binding.name
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _name_binding(node: ast.Name, provenance: ResourceProvenance) -> ast.AST | None:
|
|
247
|
+
if _type_parameter_shadowed(node.id, node, provenance):
|
|
248
|
+
return None
|
|
249
|
+
for scope in _lexical_scopes(node, provenance):
|
|
250
|
+
if bindings := provenance.bindings.get(scope, {}).get(node.id):
|
|
251
|
+
return provenance.binding(node.id, bindings[0])
|
|
252
|
+
return None
|
|
@@ -20,6 +20,7 @@ from sarj_rule_contracts import (
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from sarj_python_lint._python_target import PythonTargetFacts
|
|
23
|
+
from sarj_python_lint.suppression_policy import PROTECTED_SUPPRESSION_CODES
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
@@ -43,7 +44,7 @@ _SARJ_NOQA_RE = re.compile(
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def is_suppressed(source_lines: Sequence[str], line: int, code: str) -> bool:
|
|
46
|
-
if line < 1 or line > len(source_lines):
|
|
47
|
+
if code.upper() in PROTECTED_SUPPRESSION_CODES or line < 1 or line > len(source_lines):
|
|
47
48
|
return False
|
|
48
49
|
text = source_lines[line - 1]
|
|
49
50
|
m = _SARJ_NOQA_RE.search(text)
|
|
@@ -58,8 +59,7 @@ def is_suppressed(source_lines: Sequence[str], line: int, code: str) -> bool:
|
|
|
58
59
|
codes_str = m.group(1)
|
|
59
60
|
trailing = comment[m.end() :]
|
|
60
61
|
if codes_str is None:
|
|
61
|
-
|
|
62
|
-
return not (trailing and (not trailing[0].isspace() or trailing.lstrip().startswith(":")))
|
|
62
|
+
return False
|
|
63
63
|
reason = trailing.lstrip()
|
|
64
64
|
single_dash_reason = reason.startswith("-") and (codes_str[-1:].isspace() or trailing[:1].isspace())
|
|
65
65
|
if reason and not reason.startswith(("--", "—", "–")) and not single_dash_reason:
|
|
@@ -19,7 +19,10 @@ class AnnotationSemantics:
|
|
|
19
19
|
|
|
20
20
|
@classmethod
|
|
21
21
|
def from_tree(cls, tree: ast.Module, *, module_scope_only: bool = True) -> Self:
|
|
22
|
-
|
|
22
|
+
return cls.from_imports(tree, ImportIndex.from_tree(tree, module_scope_only=module_scope_only))
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_imports(cls, tree: ast.Module, imports: ImportIndex) -> Self:
|
|
23
26
|
candidates: dict[str, ast.expr] = {}
|
|
24
27
|
ambiguous: set[str] = set()
|
|
25
28
|
for statement in tree.body:
|
{sarj_python_lint-1.63.1 → sarj_python_lint-1.66.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
@@ -110,9 +110,7 @@ class _Models:
|
|
|
110
110
|
def validation_owner(self, unit: SourceUnit) -> _ValidationOwner:
|
|
111
111
|
if unit.path not in self._validation_owners:
|
|
112
112
|
context = PythonFileContext(unit.path, unit.source, self._validation_session)
|
|
113
|
-
self._validation_owners[unit.path] = _ValidationOwner(
|
|
114
|
-
context, ResourceProvenance(context, require_static_namespace=True)
|
|
115
|
-
)
|
|
113
|
+
self._validation_owners[unit.path] = _ValidationOwner(context, context.static_resource_provenance)
|
|
116
114
|
return self._validation_owners[unit.path]
|
|
117
115
|
|
|
118
116
|
def annotations(self, unit: SourceUnit) -> AnnotationSemantics:
|
|
@@ -378,7 +376,7 @@ class CopyOnWriteAnalysis:
|
|
|
378
376
|
|
|
379
377
|
@cached_property
|
|
380
378
|
def _validator_provider(self) -> ResourceProvenance:
|
|
381
|
-
return
|
|
379
|
+
return self.context.static_resource_provenance
|
|
382
380
|
|
|
383
381
|
def _function(self, function: _Function) -> None:
|
|
384
382
|
parameters = [*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs]
|
|
@@ -72,6 +72,7 @@ def data_union_diagnostics(context: PythonFileContext, code: str) -> list[Diagno
|
|
|
72
72
|
class _DataUnions:
|
|
73
73
|
def __init__(self, context: PythonFileContext, tree: ast.Module) -> None:
|
|
74
74
|
self.context = context
|
|
75
|
+
# This analysis refines mutation ownership; keep its provenance private.
|
|
75
76
|
self.provenance = ResourceProvenance(context, require_static_namespace=True)
|
|
76
77
|
self.provenance.mutated = _scoped_module_mutations(self.provenance, tree)
|
|
77
78
|
bindings = self.provenance.bindings.get(tree, {})
|
|
@@ -13,11 +13,11 @@ from sarj_python_lint.json_boundary import is_object_mapping
|
|
|
13
13
|
from sarj_python_lint.rules._first_party import distribution_root
|
|
14
14
|
from sarj_python_lint.rules._namespace_access import dynamic_namespace_access
|
|
15
15
|
from sarj_python_lint.rules._native_namespace_capability import native_namespace_capability_escapes
|
|
16
|
-
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
if TYPE_CHECKING:
|
|
20
19
|
from sarj_python_lint._file_context import PythonFileContext
|
|
20
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
MODEL_SOURCES = frozenset({"pydantic", "pydantic.main"})
|
|
@@ -43,7 +43,7 @@ def direct_models(context: PythonFileContext, *, methods: bool = False) -> tuple
|
|
|
43
43
|
or dynamic_namespace_access(context.nodes(ast.AST), context.imports, context.parents)
|
|
44
44
|
):
|
|
45
45
|
return ()
|
|
46
|
-
provenance =
|
|
46
|
+
provenance = context.static_resource_provenance
|
|
47
47
|
return tuple(
|
|
48
48
|
node
|
|
49
49
|
for node in tree.body
|
|
@@ -34,6 +34,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import (
|
|
|
34
34
|
from sarj_python_lint.rules.no_any_mapping_types import NoAnyMappingTypes
|
|
35
35
|
from sarj_python_lint.rules.no_async_function_cache import NoAsyncFunctionCache
|
|
36
36
|
from sarj_python_lint.rules.no_before_validator_input_mutation import NoBeforeValidatorInputMutation
|
|
37
|
+
from sarj_python_lint.rules.no_blanket_suppression import NoBlanketSuppression
|
|
37
38
|
from sarj_python_lint.rules.no_broad_keyword_capture import NoBroadKeywordCapture
|
|
38
39
|
from sarj_python_lint.rules.no_comment_cruft import NoCommentCruft
|
|
39
40
|
from sarj_python_lint.rules.no_conftest_test_module_import import NoConftestTestModuleImport
|
|
@@ -329,6 +330,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
329
330
|
RequirePydanticForExternalJson.id: RequirePydanticForExternalJson,
|
|
330
331
|
RequirePydanticForMappingShape.id: RequirePydanticForMappingShape,
|
|
331
332
|
NoDualShapeModelFieldReader.id: NoDualShapeModelFieldReader,
|
|
333
|
+
NoBlanketSuppression.id: NoBlanketSuppression,
|
|
332
334
|
PreferModuleLevelConstant.id: PreferModuleLevelConstant,
|
|
333
335
|
PreferImmutableModuleConstant.id: PreferImmutableModuleConstant,
|
|
334
336
|
PreferInjectedDependencyOverMonkeypatch.id: PreferInjectedDependencyOverMonkeypatch,
|