sarj-python-lint 1.35.0__tar.gz → 1.36.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.35.0 → sarj_python_lint-1.36.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/pyproject.toml +2 -1
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_ast_index.py +4 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_copy_on_write.py +3 -5
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_imports.py +4 -8
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +36 -1
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +10 -3
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_dunder_all.py +3 -5
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +2 -3
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +5 -4
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +65 -6
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +2 -2
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +2 -2
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +3 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/stepdown.py +3 -8
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +2 -4
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/.gitignore +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/LICENSE +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/README.md +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.36.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.36.0"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -30,6 +30,7 @@ Issues = "https://github.com/sarj-ai/code-standards/issues"
|
|
|
30
30
|
|
|
31
31
|
[dependency-groups]
|
|
32
32
|
dev = [
|
|
33
|
+
"pydantic==2.14.1",
|
|
33
34
|
"pytest>=9.1.1",
|
|
34
35
|
"ruff>=0.17.0",
|
|
35
36
|
"basedpyright>=1.40.2",
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
@@ -29,6 +29,10 @@ def children(node: ast.AST) -> list[ast.AST]:
|
|
|
29
29
|
return out
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def comprehension_results(node: ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp) -> list[ast.expr]:
|
|
33
|
+
return [child for child in children(node) if isinstance(child, ast.expr)]
|
|
34
|
+
|
|
35
|
+
|
|
32
36
|
def walk(node: ast.AST, *, index: NodeIndex | None = None) -> Iterator[ast.AST]:
|
|
33
37
|
if index is not None and index.tree is node:
|
|
34
38
|
yield from index.query((ast.AST,))
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, final
|
|
|
9
9
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
10
10
|
from sarj_python_lint._file_context import PythonFileContext
|
|
11
11
|
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
|
|
12
|
+
from sarj_python_lint.rules._ast_index import comprehension_results
|
|
12
13
|
from sarj_python_lint.rules._closed_helpers import plain_class_members
|
|
13
14
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
14
15
|
from sarj_python_lint.rules._project_index import ProjectIndexSet, SourceUnit, SymbolRef, imports_outside
|
|
@@ -695,11 +696,8 @@ class CopyOnWriteAnalysis:
|
|
|
695
696
|
self._bind(generator.target, self._iter_element(generator.iter, local), local)
|
|
696
697
|
for condition in generator.ifs:
|
|
697
698
|
self._expression(condition, local)
|
|
698
|
-
|
|
699
|
-
self._expression(
|
|
700
|
-
self._expression(node.value, local)
|
|
701
|
-
else:
|
|
702
|
-
self._expression(node.elt, local)
|
|
699
|
+
for child in comprehension_results(node):
|
|
700
|
+
self._expression(child, local)
|
|
703
701
|
|
|
704
702
|
def _iter_element(self, expression: ast.expr, bindings: Mapping[str, _Value]) -> _Value | None:
|
|
705
703
|
owner = self._value(expression, bindings)
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from types import MappingProxyType
|
|
7
7
|
from typing import Self
|
|
8
8
|
|
|
9
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
9
|
+
from sarj_python_lint.rules._ast_index import comprehension_results, walk as walk_ast
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
ABC_SOURCES = frozenset({"abc"})
|
|
@@ -138,13 +138,9 @@ def _collect_module_bindings(node: ast.AST, names: set[str]) -> None:
|
|
|
138
138
|
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
139
139
|
names.add(node.name)
|
|
140
140
|
return
|
|
141
|
-
case ast.ListComp() | ast.SetComp() | ast.GeneratorExp():
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return
|
|
145
|
-
case ast.DictComp():
|
|
146
|
-
_collect_module_bindings(node.key, names)
|
|
147
|
-
_collect_module_bindings(node.value, names)
|
|
141
|
+
case ast.ListComp() | ast.SetComp() | ast.GeneratorExp() | ast.DictComp():
|
|
142
|
+
for result in comprehension_results(node):
|
|
143
|
+
_collect_module_bindings(result, names)
|
|
148
144
|
_collect_comprehension_bindings(node.generators, names)
|
|
149
145
|
return
|
|
150
146
|
case ast.Lambda():
|
|
@@ -25,6 +25,8 @@ DECORATOR_SOURCES = frozenset({"pydantic", "pydantic.functional_validators", "py
|
|
|
25
25
|
TYPING_SOURCES = frozenset({"typing", "typing_extensions"})
|
|
26
26
|
_ALIAS_VERSION = Version("2.11")
|
|
27
27
|
_BUILTIN_BINDINGS = frozenset({"int", "float", "str", "list", "classmethod", "staticmethod"})
|
|
28
|
+
_GENERIC_BUILTINS = frozenset({"dict", "frozenset", "list", "set", "tuple", "type"})
|
|
29
|
+
_BUILTIN_SOURCES = frozenset({"builtins"})
|
|
28
30
|
_SENSITIVE_MODULES = frozenset({"pydantic", "typing", "typing_extensions", "builtins"})
|
|
29
31
|
|
|
30
32
|
|
|
@@ -76,6 +78,8 @@ def _plain_member(node: ast.stmt, context: PythonFileContext, provenance: Resour
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
def inert_annotation(node: ast.expr, context: PythonFileContext) -> bool:
|
|
81
|
+
if isinstance(node, (ast.Tuple, ast.List)):
|
|
82
|
+
return all(inert_annotation(element, context) for element in node.elts)
|
|
79
83
|
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
80
84
|
try:
|
|
81
85
|
node = ast.parse(node.value, mode="eval").body
|
|
@@ -104,6 +108,8 @@ def inert_signature(node: ast.FunctionDef | ast.AsyncFunctionDef, context: Pytho
|
|
|
104
108
|
|
|
105
109
|
|
|
106
110
|
def _inert_expr(node: ast.AST, context: PythonFileContext) -> bool:
|
|
111
|
+
if isinstance(node, ast.Subscript) and not _inert_subscription(node, context):
|
|
112
|
+
return False
|
|
107
113
|
if isinstance(node, ast.Lambda):
|
|
108
114
|
return not node.args.defaults and not node.args.kw_defaults
|
|
109
115
|
if isinstance(node, ast.Call):
|
|
@@ -121,12 +127,41 @@ def _inert_expr(node: ast.AST, context: PythonFileContext) -> bool:
|
|
|
121
127
|
return False
|
|
122
128
|
if isinstance(
|
|
123
129
|
node,
|
|
124
|
-
(
|
|
130
|
+
(
|
|
131
|
+
ast.NamedExpr,
|
|
132
|
+
ast.Starred,
|
|
133
|
+
ast.Await,
|
|
134
|
+
ast.Yield,
|
|
135
|
+
ast.YieldFrom,
|
|
136
|
+
ast.ListComp,
|
|
137
|
+
ast.SetComp,
|
|
138
|
+
ast.DictComp,
|
|
139
|
+
ast.GeneratorExp,
|
|
140
|
+
),
|
|
125
141
|
):
|
|
126
142
|
return False
|
|
127
143
|
return all(_inert_expr(child, context) for child in ast.iter_child_nodes(node))
|
|
128
144
|
|
|
129
145
|
|
|
146
|
+
def _inert_subscription(node: ast.Subscript, context: PythonFileContext) -> bool:
|
|
147
|
+
symbol = context.imports.resolved_symbol(node.value, sources=TYPING_SOURCES)
|
|
148
|
+
if symbol is None and not (
|
|
149
|
+
(
|
|
150
|
+
isinstance(node.value, ast.Name)
|
|
151
|
+
and node.value.id in _GENERIC_BUILTINS
|
|
152
|
+
and context.imports.builtin_is_unshadowed(node.value.id)
|
|
153
|
+
)
|
|
154
|
+
or context.imports.resolved_symbol(node.value, sources=_BUILTIN_SOURCES) in _GENERIC_BUILTINS
|
|
155
|
+
):
|
|
156
|
+
return False
|
|
157
|
+
arguments = node.slice.elts if isinstance(node.slice, ast.Tuple) else [node.slice]
|
|
158
|
+
if symbol == "Literal":
|
|
159
|
+
return True
|
|
160
|
+
if symbol == "Annotated":
|
|
161
|
+
return bool(arguments) and inert_annotation(arguments[0], context)
|
|
162
|
+
return all(inert_annotation(argument, context) for argument in arguments)
|
|
163
|
+
|
|
164
|
+
|
|
130
165
|
def literal_value(value: ast.expr) -> bool:
|
|
131
166
|
if any(isinstance(node, ast.Call) for node in ast.walk(value)):
|
|
132
167
|
return False
|
|
@@ -4,6 +4,7 @@ import ast
|
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import PurePosixPath
|
|
7
|
+
import re
|
|
7
8
|
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
8
9
|
|
|
9
10
|
from sarj_python_lint.rule_base import (
|
|
@@ -38,6 +39,7 @@ _MAPPING_NAMES = frozenset({"dict", "Dict", "Mapping", "MutableMapping"})
|
|
|
38
39
|
_MAPPING_ARGUMENT_COUNT = 2
|
|
39
40
|
_BUILTIN_TYPE_CONTAINERS = frozenset({"dict", "list", "set", "frozenset", "tuple", "type"})
|
|
40
41
|
_TYPE_MODULES = frozenset({"typing", "typing_extensions", "pydantic", "collections", "builtins"})
|
|
42
|
+
_TYPE_COMMENT_MARKER = re.compile(r"#\s*type:")
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
@dataclass(frozen=True, slots=True)
|
|
@@ -125,9 +127,14 @@ class NoAnyMappingTypes(Rule):
|
|
|
125
127
|
source = context.source
|
|
126
128
|
if context.generated or _is_vendor_path(path):
|
|
127
129
|
return []
|
|
128
|
-
|
|
129
|
-
tree =
|
|
130
|
-
|
|
130
|
+
if _TYPE_COMMENT_MARKER.search(source) is None:
|
|
131
|
+
tree = context.tree
|
|
132
|
+
else:
|
|
133
|
+
try:
|
|
134
|
+
tree = ast.parse(source, filename=str(path), type_comments=True)
|
|
135
|
+
except SyntaxError:
|
|
136
|
+
return []
|
|
137
|
+
if tree is None:
|
|
131
138
|
return []
|
|
132
139
|
semantics = AnnotationSemantics.from_tree(tree)
|
|
133
140
|
imports = semantics.imports
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
@@ -14,6 +14,7 @@ from sarj_python_lint.rule_base import (
|
|
|
14
14
|
RuleDocumentation,
|
|
15
15
|
RuleExample,
|
|
16
16
|
)
|
|
17
|
+
from sarj_python_lint.rules._ast_index import comprehension_results
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
@@ -344,11 +345,8 @@ class _DunderAllVisitor(_ScopePruningVisitor):
|
|
|
344
345
|
self.generic_visit(node)
|
|
345
346
|
|
|
346
347
|
def _visit_comprehension(self, node: ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp) -> None:
|
|
347
|
-
|
|
348
|
-
self.visit(
|
|
349
|
-
self.visit(node.value)
|
|
350
|
-
else:
|
|
351
|
-
self.visit(node.elt)
|
|
348
|
+
for result in comprehension_results(node):
|
|
349
|
+
self.visit(result)
|
|
352
350
|
for generator in node.generators:
|
|
353
351
|
self.visit(generator.iter)
|
|
354
352
|
for condition in generator.ifs:
|
|
@@ -15,7 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleExample,
|
|
16
16
|
Severity,
|
|
17
17
|
)
|
|
18
|
-
from sarj_python_lint.rules._ast_index import children
|
|
18
|
+
from sarj_python_lint.rules._ast_index import children, comprehension_results
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -154,8 +154,7 @@ class _Complexity:
|
|
|
154
154
|
self.add(condition, nesting, "comprehension filter")
|
|
155
155
|
self.visit(condition, nesting)
|
|
156
156
|
nesting += 1
|
|
157
|
-
|
|
158
|
-
self.visit_all(values, nesting)
|
|
157
|
+
self.visit_all(comprehension_results(node), nesting)
|
|
159
158
|
|
|
160
159
|
|
|
161
160
|
@final
|
|
@@ -18,6 +18,7 @@ from sarj_python_lint.rule_base import (
|
|
|
18
18
|
RuleExample,
|
|
19
19
|
Severity,
|
|
20
20
|
)
|
|
21
|
+
from sarj_python_lint.rules._ast_index import comprehension_results
|
|
21
22
|
from sarj_python_lint.rules._first_party import FirstPartyFacts, distribution_root
|
|
22
23
|
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
23
24
|
|
|
@@ -870,8 +871,8 @@ def _calls_with_shadowing(
|
|
|
870
871
|
case ast.ListComp() | ast.SetComp() | ast.GeneratorExp():
|
|
871
872
|
yield from _comprehension_calls(node.generators, (node.elt,), shadowed, nested_scope_base)
|
|
872
873
|
return
|
|
873
|
-
case ast.DictComp(generators=generators
|
|
874
|
-
yield from _comprehension_calls(generators, (
|
|
874
|
+
case ast.DictComp(generators=generators):
|
|
875
|
+
yield from _comprehension_calls(generators, tuple(comprehension_results(node)), shadowed, nested_scope_base)
|
|
875
876
|
return
|
|
876
877
|
case ast.Lambda(args=args, body=body):
|
|
877
878
|
outer_nodes = [*args.defaults, *(default for default in args.kw_defaults if default is not None)]
|
|
@@ -1008,8 +1009,8 @@ class LocalBindingCollector(ast.NodeVisitor):
|
|
|
1008
1009
|
|
|
1009
1010
|
@override
|
|
1010
1011
|
def visit_DictComp(self, node: ast.DictComp) -> None:
|
|
1011
|
-
|
|
1012
|
-
|
|
1012
|
+
for result in comprehension_results(node):
|
|
1013
|
+
self.visit(result)
|
|
1013
1014
|
self._visit_generators(node.generators)
|
|
1014
1015
|
|
|
1015
1016
|
@override
|
|
@@ -28,6 +28,8 @@ if TYPE_CHECKING:
|
|
|
28
28
|
_NUMERIC_BOUNDS = frozenset({"gt", "ge", "lt", "le"})
|
|
29
29
|
_LENGTH_BOUNDS = frozenset({"min_length", "max_length"})
|
|
30
30
|
_KINDS = frozenset({"int", "float", "str", "list"})
|
|
31
|
+
_PASSIVE_CONFIG_BOOLEANS = frozenset({"strict", "frozen", "validate_default"})
|
|
32
|
+
_EXTRA_POLICIES = frozenset({"allow", "forbid", "ignore"})
|
|
31
33
|
_PASSIVE_OPTIONS = frozenset(
|
|
32
34
|
{
|
|
33
35
|
"default",
|
|
@@ -59,13 +61,15 @@ class NoImpossiblePydanticFieldBounds(Rule):
|
|
|
59
61
|
documentation = RuleDocumentation(
|
|
60
62
|
default_level=Severity.ERROR,
|
|
61
63
|
summary="Pydantic field bounds must admit at least one value.",
|
|
62
|
-
rationale="Contradictory literal constraints reject every field value, including integer intervals with no
|
|
64
|
+
rationale="Contradictory literal constraints reject every field value, including integer multiples and float intervals with no representable member.",
|
|
63
65
|
remediation="Correct the conflicting bounds so the declared field domain admits the intended values.",
|
|
64
66
|
category=RuleCategory.CORRECTNESS,
|
|
65
67
|
autofix=AutofixPolicy.NONE,
|
|
66
68
|
limitations=(
|
|
67
69
|
"Only plain direct module-level BaseModel subclasses and built-in int, float, str and list fields are inspected.",
|
|
68
70
|
"Bounds must be finite literals; int and length bounds must be integers, and float bounds must convert exactly.",
|
|
71
|
+
"Positive integer multiple_of constraints use exact integer arithmetic; floating-point steps remain outside the rule.",
|
|
72
|
+
"Literal strict, frozen, validate_default and extra model settings preserve coverage; other or dynamic configuration is excluded.",
|
|
69
73
|
"Unknown metadata, nullable types, repeated bound names, methods, dynamic namespaces and generated files are excluded.",
|
|
70
74
|
"Visible namespace APIs, dynamic module access, ambiguous imports and escaped or mutated imported objects exclude the file.",
|
|
71
75
|
"No autofix chooses a domain or changes validation behavior.",
|
|
@@ -130,7 +134,7 @@ class NoImpossiblePydanticFieldBounds(Rule):
|
|
|
130
134
|
def _candidate_fields(context: PythonFileContext) -> list[ast.AnnAssign]:
|
|
131
135
|
fields: list[ast.AnnAssign] = []
|
|
132
136
|
for model in direct_models(context):
|
|
133
|
-
if
|
|
137
|
+
if not _passive_model_configuration(model, context):
|
|
134
138
|
continue
|
|
135
139
|
unique = unique_members(model)
|
|
136
140
|
fields.extend(
|
|
@@ -144,11 +148,50 @@ def _candidate_fields(context: PythonFileContext) -> list[ast.AnnAssign]:
|
|
|
144
148
|
return fields
|
|
145
149
|
|
|
146
150
|
|
|
151
|
+
def _passive_model_configuration(model: ast.ClassDef, context: PythonFileContext) -> bool:
|
|
152
|
+
configurations = [
|
|
153
|
+
member for member in model.body if isinstance(member, (ast.Assign, ast.AnnAssign)) and _names_config(member)
|
|
154
|
+
]
|
|
155
|
+
if not configurations:
|
|
156
|
+
return True
|
|
157
|
+
if len(configurations) != 1:
|
|
158
|
+
return False
|
|
159
|
+
options = _configuration_options(configurations[0].value, context)
|
|
160
|
+
return options is not None and all(
|
|
161
|
+
isinstance(item, ast.Constant)
|
|
162
|
+
and (
|
|
163
|
+
(name in _PASSIVE_CONFIG_BOOLEANS and type(item.value) is bool)
|
|
164
|
+
or (name == "extra" and isinstance(item.value, str) and item.value in _EXTRA_POLICIES)
|
|
165
|
+
)
|
|
166
|
+
for name, item in options
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
147
170
|
def _names_config(node: ast.Assign | ast.AnnAssign) -> bool:
|
|
148
171
|
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
|
149
172
|
return any(isinstance(target, ast.Name) and target.id == "model_config" for target in targets)
|
|
150
173
|
|
|
151
174
|
|
|
175
|
+
def _configuration_options(value: ast.expr | None, context: PythonFileContext) -> list[tuple[str, ast.expr]] | None:
|
|
176
|
+
if isinstance(value, ast.Dict):
|
|
177
|
+
options: list[tuple[str, ast.expr]] = []
|
|
178
|
+
for key, item in zip(value.keys, value.values, strict=True):
|
|
179
|
+
if not isinstance(key, ast.Constant) or not isinstance(key.value, str):
|
|
180
|
+
return None
|
|
181
|
+
options.append((key.value, item))
|
|
182
|
+
return options
|
|
183
|
+
if (
|
|
184
|
+
not isinstance(value, ast.Call)
|
|
185
|
+
or value.args
|
|
186
|
+
or not context.module_imports.resolves(
|
|
187
|
+
value.func, sources=frozenset({"pydantic", "pydantic.config"}), symbol="ConfigDict"
|
|
188
|
+
)
|
|
189
|
+
or any(keyword.arg is None for keyword in value.keywords)
|
|
190
|
+
):
|
|
191
|
+
return None
|
|
192
|
+
return [(keyword.arg, keyword.value) for keyword in value.keywords if keyword.arg is not None]
|
|
193
|
+
|
|
194
|
+
|
|
152
195
|
def _field_contract(field: ast.AnnAssign, context: PythonFileContext) -> tuple[str, dict[str, int | float]] | None:
|
|
153
196
|
annotation = field.annotation
|
|
154
197
|
calls: list[ast.Call] = []
|
|
@@ -186,7 +229,9 @@ def _literal_bounds(calls: list[ast.Call], kind: str) -> dict[str, int | float]
|
|
|
186
229
|
if any(call.args for call in calls):
|
|
187
230
|
return None
|
|
188
231
|
bounds: dict[str, int | float] = {}
|
|
189
|
-
allowed =
|
|
232
|
+
allowed = (
|
|
233
|
+
_NUMERIC_BOUNDS | {"multiple_of"} if kind == "int" else _NUMERIC_BOUNDS if kind == "float" else _LENGTH_BOUNDS
|
|
234
|
+
)
|
|
190
235
|
for keyword in (keyword for call in calls for keyword in call.keywords):
|
|
191
236
|
name = keyword.arg
|
|
192
237
|
if name in _PASSIVE_OPTIONS:
|
|
@@ -194,7 +239,7 @@ def _literal_bounds(calls: list[ast.Call], kind: str) -> dict[str, int | float]
|
|
|
194
239
|
if name is None or name not in allowed or name in bounds:
|
|
195
240
|
return None
|
|
196
241
|
value = _bound_value(keyword.value, kind)
|
|
197
|
-
if value is None:
|
|
242
|
+
if value is None or (name == "multiple_of" and value <= 0):
|
|
198
243
|
return None
|
|
199
244
|
bounds[name] = value
|
|
200
245
|
return bounds
|
|
@@ -240,5 +285,19 @@ def _empty_interval(kind: str, bounds: dict[str, int | float]) -> bool:
|
|
|
240
285
|
lower = max(lowers, key=lambda bound: (bound.value, bound.exclusive))
|
|
241
286
|
upper = min(uppers, key=lambda bound: (bound.value, not bound.exclusive))
|
|
242
287
|
if kind != "float":
|
|
243
|
-
|
|
244
|
-
|
|
288
|
+
minimum = lower.value + int(lower.exclusive)
|
|
289
|
+
maximum = upper.value - int(upper.exclusive)
|
|
290
|
+
if minimum > maximum:
|
|
291
|
+
return True
|
|
292
|
+
multiple = bounds.get("multiple_of")
|
|
293
|
+
return (
|
|
294
|
+
kind == "int"
|
|
295
|
+
and isinstance(minimum, int)
|
|
296
|
+
and isinstance(maximum, int)
|
|
297
|
+
and isinstance(multiple, int)
|
|
298
|
+
and multiple > 0
|
|
299
|
+
and -(-minimum // multiple) * multiple > maximum
|
|
300
|
+
)
|
|
301
|
+
minimum_float = math.nextafter(lower.value, math.inf) if lower.exclusive else float(lower.value)
|
|
302
|
+
maximum_float = math.nextafter(upper.value, -math.inf) if upper.exclusive else float(upper.value)
|
|
303
|
+
return minimum_float > maximum_float
|
|
@@ -18,7 +18,7 @@ from sarj_python_lint.rule_base import (
|
|
|
18
18
|
Severity,
|
|
19
19
|
is_suppressed,
|
|
20
20
|
)
|
|
21
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
21
|
+
from sarj_python_lint.rules._ast_index import comprehension_results, walk as walk_ast
|
|
22
22
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
23
23
|
|
|
24
24
|
|
|
@@ -611,7 +611,7 @@ class _FunctionAnalyzer:
|
|
|
611
611
|
|
|
612
612
|
@override
|
|
613
613
|
def visit_DictComp(self, node: ast.DictComp) -> None:
|
|
614
|
-
self._comprehension(node.generators,
|
|
614
|
+
self._comprehension(node.generators, comprehension_results(node))
|
|
615
615
|
|
|
616
616
|
def _comprehension(self, generators: list[ast.comprehension], results: list[ast.expr]) -> None:
|
|
617
617
|
saved_shadowed = set(analyzer._shadowed)
|
|
@@ -19,7 +19,7 @@ from sarj_python_lint.rule_base import (
|
|
|
19
19
|
Severity,
|
|
20
20
|
is_suppressed,
|
|
21
21
|
)
|
|
22
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
22
|
+
from sarj_python_lint.rules._ast_index import comprehension_results, walk as walk_ast
|
|
23
23
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
24
24
|
from sarj_python_lint.rules._paths import is_test_path
|
|
25
25
|
|
|
@@ -1060,7 +1060,7 @@ def _comprehension_oracle(
|
|
|
1060
1060
|
flow = _bind_comprehension_target(generator, flow)
|
|
1061
1061
|
if any(_raw_text_oracle(condition, flow) for condition in generator.ifs):
|
|
1062
1062
|
return True
|
|
1063
|
-
values =
|
|
1063
|
+
values = comprehension_results(node)
|
|
1064
1064
|
return any(
|
|
1065
1065
|
_raw_text_oracle(value, flow) or (consumes_values and _raw_text_expression(value, flow)) for value in values
|
|
1066
1066
|
)
|
|
@@ -69,6 +69,9 @@ def _reference_tokens(value: ast.expr) -> tuple[str, ...]:
|
|
|
69
69
|
return tuple(identifier_tokens(name))
|
|
70
70
|
case ast.Attribute(value=receiver, attr=attribute):
|
|
71
71
|
return (*_reference_tokens(receiver), *identifier_tokens(attribute))
|
|
72
|
+
case ast.Subscript(value=receiver, slice=ast.Constant(value=str() as key)):
|
|
73
|
+
metadata = tuple(word for word in identifier_tokens(key) if word in _SAFE_METADATA or word == "not")
|
|
74
|
+
return (*_reference_tokens(receiver), *metadata)
|
|
72
75
|
case ast.Subscript(value=receiver):
|
|
73
76
|
return _reference_tokens(receiver)
|
|
74
77
|
case ast.Await(value=awaited):
|
|
@@ -16,7 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
RuleDocumentation,
|
|
17
17
|
RuleExample,
|
|
18
18
|
)
|
|
19
|
-
from sarj_python_lint.rules._ast_index import children
|
|
19
|
+
from sarj_python_lint.rules._ast_index import children, comprehension_results
|
|
20
20
|
from sarj_python_lint.rules._paths import is_test_path
|
|
21
21
|
|
|
22
22
|
|
|
@@ -571,11 +571,7 @@ def _comprehension_expressions(
|
|
|
571
571
|
for generator in node.generators:
|
|
572
572
|
yield generator.iter
|
|
573
573
|
yield from generator.ifs
|
|
574
|
-
|
|
575
|
-
yield node.key
|
|
576
|
-
yield node.value
|
|
577
|
-
else:
|
|
578
|
-
yield node.elt
|
|
574
|
+
yield from comprehension_results(node)
|
|
579
575
|
|
|
580
576
|
|
|
581
577
|
def _module_pinned_names(tree: ast.Module, definition_names: frozenset[str]) -> set[str]:
|
|
@@ -712,8 +708,7 @@ def _resolved_comprehension_loads(
|
|
|
712
708
|
yield from _resolved_loads((generator.iter,), candidates, comp_blocked)
|
|
713
709
|
comp_blocked.update(_target_names(generator.target) & candidates)
|
|
714
710
|
yield from _resolved_loads(generator.ifs, candidates, comp_blocked)
|
|
715
|
-
|
|
716
|
-
yield from _resolved_loads(values, candidates, comp_blocked)
|
|
711
|
+
yield from _resolved_loads(comprehension_results(node), candidates, comp_blocked)
|
|
717
712
|
|
|
718
713
|
|
|
719
714
|
def _direct_scope_bindings(node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]:
|
|
@@ -16,6 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
RuleExample,
|
|
17
17
|
Severity,
|
|
18
18
|
)
|
|
19
|
+
from sarj_python_lint.rules._ast_index import comprehension_results
|
|
19
20
|
from sarj_python_lint.rules._paths import is_test_path
|
|
20
21
|
|
|
21
22
|
|
|
@@ -444,10 +445,7 @@ def _repeated_comprehension_regions(
|
|
|
444
445
|
regions.extend(generator.ifs)
|
|
445
446
|
prior_generator_repeats = prior_generator_repeats or this_generator_repeats
|
|
446
447
|
if prior_generator_repeats:
|
|
447
|
-
|
|
448
|
-
regions.extend((node.key, node.value))
|
|
449
|
-
else:
|
|
450
|
-
regions.append(node.elt)
|
|
448
|
+
regions.extend(comprehension_results(node))
|
|
451
449
|
return tuple(regions)
|
|
452
450
|
|
|
453
451
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.35.0 → sarj_python_lint-1.36.0}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|