sarj-python-lint 1.56.0__tar.gz → 1.57.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.56.0 → sarj_python_lint-1.57.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/pyproject.toml +1 -1
- sarj_python_lint-1.57.0/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +260 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +40 -118
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/.gitignore +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/LICENSE +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/README.md +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.56.0 → sarj_python_lint-1.57.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.57.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/
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from typing import TYPE_CHECKING, final
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
|
|
10
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
11
|
+
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
type _Function = ast.FunctionDef | ast.AsyncFunctionDef
|
|
15
|
+
_VALIDATORS = frozenset(
|
|
16
|
+
{
|
|
17
|
+
"pydantic.model_validator",
|
|
18
|
+
"pydantic.field_validator",
|
|
19
|
+
"pydantic.functional_validators.model_validator",
|
|
20
|
+
"pydantic.functional_validators.field_validator",
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
_DISCRIMINATORS = frozenset({"pydantic.Discriminator", "pydantic.types.Discriminator"})
|
|
24
|
+
_RAW_METADATA = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"pydantic.BeforeValidator",
|
|
27
|
+
"pydantic.WrapValidator",
|
|
28
|
+
"pydantic.functional_validators.BeforeValidator",
|
|
29
|
+
"pydantic.functional_validators.WrapValidator",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
_MAX_DEPTH = 32
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@final
|
|
36
|
+
class RawMappingCallbacks:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
context: PythonFileContext,
|
|
40
|
+
provenance: ResourceProvenance,
|
|
41
|
+
qualified: Callable[[ast.expr, ast.AST], str | None],
|
|
42
|
+
builtin: Callable[[ast.expr, str, ast.AST], bool],
|
|
43
|
+
shadowed: Callable[[str, ast.AST], bool],
|
|
44
|
+
) -> None:
|
|
45
|
+
self.context = context
|
|
46
|
+
self.provenance = provenance
|
|
47
|
+
self.qualified = qualified
|
|
48
|
+
self.builtin = builtin
|
|
49
|
+
self.shadowed = shadowed
|
|
50
|
+
self.ownership: dict[_Function, bool] = {}
|
|
51
|
+
self.callbacks = {
|
|
52
|
+
binding
|
|
53
|
+
for call in context.nodes(ast.Call)
|
|
54
|
+
if (origin := qualified(call.func, call)) in _DISCRIMINATORS | _RAW_METADATA
|
|
55
|
+
if (argument := _callback_argument(call, origin)) is not None
|
|
56
|
+
if (binding := self.callback(argument)) is not None
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def callback(self, node: ast.expr, *, seen: frozenset[ast.AST] = frozenset()) -> _Function | None:
|
|
60
|
+
if len(seen) >= _MAX_DEPTH:
|
|
61
|
+
return None
|
|
62
|
+
if isinstance(node, ast.Call):
|
|
63
|
+
return (
|
|
64
|
+
self._partial(node, seen)
|
|
65
|
+
if self.qualified(node.func, node) == "functools.partial"
|
|
66
|
+
else self._method(node, "__call__", seen)
|
|
67
|
+
)
|
|
68
|
+
if isinstance(node, ast.Attribute):
|
|
69
|
+
return self._method(node.value, node.attr, seen)
|
|
70
|
+
if not isinstance(node, ast.Name) or not self._stable(node.id) or self.shadowed(node.id, node):
|
|
71
|
+
return None
|
|
72
|
+
binding = self.provenance.binding(node.id, node)
|
|
73
|
+
if isinstance(binding, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
74
|
+
return binding if not binding.decorator_list else None
|
|
75
|
+
if not isinstance(binding, ast.Name) or binding in seen:
|
|
76
|
+
return None
|
|
77
|
+
value = self.provenance.assignment_value(binding, node)
|
|
78
|
+
return self.callback(value, seen=seen | {binding}) if value is not None else None
|
|
79
|
+
|
|
80
|
+
def _partial(self, node: ast.Call, seen: frozenset[ast.AST]) -> _Function | None:
|
|
81
|
+
if len(node.args) != 1 or isinstance(node.args[0], ast.Starred):
|
|
82
|
+
return None
|
|
83
|
+
callback = self.callback(node.args[0], seen=seen | {node})
|
|
84
|
+
if callback is None or not (callback.args.posonlyargs or callback.args.args):
|
|
85
|
+
return None
|
|
86
|
+
configuration = {argument.arg for argument in callback.args.kwonlyargs}
|
|
87
|
+
return callback if all(keyword.arg in configuration for keyword in node.keywords) else None
|
|
88
|
+
|
|
89
|
+
def _stable(self, name: str) -> bool:
|
|
90
|
+
return name not in self.provenance.mutated | self.provenance.external_bindings
|
|
91
|
+
|
|
92
|
+
def _method(self, node: ast.expr, name: str, seen: frozenset[ast.AST]) -> ast.FunctionDef | None:
|
|
93
|
+
owner = self._owner(node, seen)
|
|
94
|
+
if owner is None:
|
|
95
|
+
return None
|
|
96
|
+
declaration, on_class = owner
|
|
97
|
+
if declaration.bases or declaration.keywords or declaration.type_params:
|
|
98
|
+
return None
|
|
99
|
+
if (
|
|
100
|
+
not isinstance(self.context.parents.get(declaration), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef)
|
|
101
|
+
or not self._stable(declaration.name)
|
|
102
|
+
or any(
|
|
103
|
+
self.qualified(decorator, decorator) not in {"typing.final", "typing_extensions.final"}
|
|
104
|
+
for decorator in declaration.decorator_list
|
|
105
|
+
)
|
|
106
|
+
):
|
|
107
|
+
return None
|
|
108
|
+
method = _plain_method(declaration, name)
|
|
109
|
+
if method is None:
|
|
110
|
+
return None
|
|
111
|
+
if not method.decorator_list:
|
|
112
|
+
return method if not on_class else None
|
|
113
|
+
if len(method.decorator_list) != 1:
|
|
114
|
+
return None
|
|
115
|
+
decorator = method.decorator_list[0]
|
|
116
|
+
return (
|
|
117
|
+
method
|
|
118
|
+
if any(self.builtin(decorator, kind, decorator) for kind in ("staticmethod", "classmethod"))
|
|
119
|
+
else None
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _owner(self, node: ast.expr, seen: frozenset[ast.AST]) -> tuple[ast.ClassDef, bool] | None:
|
|
123
|
+
if len(seen) >= _MAX_DEPTH:
|
|
124
|
+
return None
|
|
125
|
+
if isinstance(node, ast.Call):
|
|
126
|
+
if node.args or node.keywords or not isinstance(node.func, ast.Name):
|
|
127
|
+
return None
|
|
128
|
+
owner = self._owner(node.func, seen | {node})
|
|
129
|
+
return (owner[0], False) if owner is not None and owner[1] else None
|
|
130
|
+
if not isinstance(node, ast.Name) or not self._stable(node.id) or self.shadowed(node.id, node):
|
|
131
|
+
return None
|
|
132
|
+
binding = self.provenance.binding(node.id, node)
|
|
133
|
+
if isinstance(binding, ast.ClassDef):
|
|
134
|
+
return binding, True
|
|
135
|
+
if not isinstance(binding, ast.Name) or binding in seen:
|
|
136
|
+
return None
|
|
137
|
+
value = self.provenance.assignment_value(binding, node)
|
|
138
|
+
return self._owner(value, seen | {binding}) if value is not None else None
|
|
139
|
+
|
|
140
|
+
def owns(self, node: ast.AST) -> bool:
|
|
141
|
+
function = self._function(node)
|
|
142
|
+
return function is not None and self._owned(function, frozenset())
|
|
143
|
+
|
|
144
|
+
def _function(self, node: ast.AST) -> _Function | None:
|
|
145
|
+
current = self.context.parents.get(node)
|
|
146
|
+
while current is not None:
|
|
147
|
+
if isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
148
|
+
return current
|
|
149
|
+
if isinstance(current, ast.ClassDef | ast.Lambda):
|
|
150
|
+
return None
|
|
151
|
+
current = self.context.parents.get(current)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def _owned(self, function: _Function, seen: frozenset[ast.AST]) -> bool:
|
|
155
|
+
if function in self.ownership:
|
|
156
|
+
return self.ownership[function]
|
|
157
|
+
if function in seen or len(seen) >= _MAX_DEPTH:
|
|
158
|
+
return False
|
|
159
|
+
result = self._raw_validator(function)
|
|
160
|
+
if not result:
|
|
161
|
+
owner = self._function(function)
|
|
162
|
+
result = owner is not None and self._owned(owner, seen | {function}) and self._local_helper(function, owner)
|
|
163
|
+
self.ownership[function] = result
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
def _local_helper(self, function: _Function, owner: _Function) -> bool:
|
|
167
|
+
if (
|
|
168
|
+
function.decorator_list
|
|
169
|
+
or self.context.parents.get(function) is not owner
|
|
170
|
+
or not self._stable(function.name)
|
|
171
|
+
):
|
|
172
|
+
return False
|
|
173
|
+
reads = [
|
|
174
|
+
read
|
|
175
|
+
for read in self.provenance.reads.get(function.name, ())
|
|
176
|
+
if self.provenance.binding(function.name, read) is function
|
|
177
|
+
]
|
|
178
|
+
return bool(reads) and all(
|
|
179
|
+
isinstance(parent := self.context.parents.get(read), ast.Call)
|
|
180
|
+
and parent.func is read
|
|
181
|
+
and self._function(read) in {owner, function}
|
|
182
|
+
for read in reads
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _raw_validator(self, function: _Function) -> bool:
|
|
186
|
+
if function in self.callbacks:
|
|
187
|
+
return True
|
|
188
|
+
return any(
|
|
189
|
+
isinstance(decorator, ast.Call)
|
|
190
|
+
and self.qualified(decorator.func, decorator) in _VALIDATORS
|
|
191
|
+
and any(
|
|
192
|
+
keyword.arg == "mode" and self._mode(keyword.value) in {"before", "wrap"}
|
|
193
|
+
for keyword in decorator.keywords
|
|
194
|
+
)
|
|
195
|
+
for decorator in function.decorator_list
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _mode(self, node: ast.expr, *, seen: frozenset[ast.AST] = frozenset()) -> str | None:
|
|
199
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
200
|
+
return node.value
|
|
201
|
+
if (
|
|
202
|
+
len(seen) >= _MAX_DEPTH
|
|
203
|
+
or not isinstance(node, ast.Name)
|
|
204
|
+
or not self._stable(node.id)
|
|
205
|
+
or self.shadowed(node.id, node)
|
|
206
|
+
):
|
|
207
|
+
return None
|
|
208
|
+
binding = self.provenance.binding(node.id, node)
|
|
209
|
+
if not isinstance(binding, ast.Name) or binding in seen:
|
|
210
|
+
return None
|
|
211
|
+
statement = self.context.parents.get(binding)
|
|
212
|
+
if not isinstance(statement, ast.Assign | ast.AnnAssign):
|
|
213
|
+
return None
|
|
214
|
+
if (
|
|
215
|
+
(isinstance(statement, ast.Assign) and len(statement.targets) != 1)
|
|
216
|
+
or statement.value is None
|
|
217
|
+
or statement.lineno >= node.lineno
|
|
218
|
+
or not isinstance(
|
|
219
|
+
self.context.parents.get(statement), ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef
|
|
220
|
+
)
|
|
221
|
+
):
|
|
222
|
+
return None
|
|
223
|
+
return self._mode(statement.value, seen=seen | {binding})
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _callback_argument(call: ast.Call, qualified: str | None) -> ast.expr | None:
|
|
227
|
+
if call.args:
|
|
228
|
+
return call.args[0]
|
|
229
|
+
parameter = "discriminator" if qualified in _DISCRIMINATORS else "func"
|
|
230
|
+
return next((keyword.value for keyword in call.keywords if keyword.arg == parameter), None)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _plain_method(node: ast.ClassDef, name: str) -> ast.FunctionDef | None:
|
|
234
|
+
method: ast.FunctionDef | None = None
|
|
235
|
+
for statement in node.body:
|
|
236
|
+
match statement:
|
|
237
|
+
case ast.FunctionDef(name=declared) if declared == name and method is None:
|
|
238
|
+
method = statement
|
|
239
|
+
case ast.Assign(value=value, targets=targets) if all(
|
|
240
|
+
isinstance(target, ast.Name) and target.id != name and _inert_attribute(target.id, value)
|
|
241
|
+
for target in targets
|
|
242
|
+
):
|
|
243
|
+
continue
|
|
244
|
+
case ast.AnnAssign(target=ast.Name(id=attribute), value=value) if attribute != name and _inert_attribute(
|
|
245
|
+
attribute, value
|
|
246
|
+
):
|
|
247
|
+
continue
|
|
248
|
+
case ast.Pass() | ast.Expr(value=ast.Constant(value=str())):
|
|
249
|
+
continue
|
|
250
|
+
case _:
|
|
251
|
+
return None
|
|
252
|
+
return method
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _inert_attribute(name: str, value: ast.expr | None) -> bool:
|
|
256
|
+
if name == "__slots__":
|
|
257
|
+
return isinstance(value, ast.Tuple) and not value.elts
|
|
258
|
+
return (name == "__name__" or not (name.startswith("__") and name.endswith("__"))) and (
|
|
259
|
+
value is None or isinstance(value, ast.Constant)
|
|
260
|
+
)
|
|
@@ -18,6 +18,7 @@ from sarj_python_lint.rule_base import (
|
|
|
18
18
|
)
|
|
19
19
|
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
|
|
20
20
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
21
|
+
from sarj_python_lint.rules._mapping_shape_callbacks import RawMappingCallbacks
|
|
21
22
|
from sarj_python_lint.rules._paths import is_test_path
|
|
22
23
|
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
23
24
|
|
|
@@ -39,23 +40,6 @@ _MAPPINGS = frozenset(
|
|
|
39
40
|
}
|
|
40
41
|
)
|
|
41
42
|
_WRAPPERS = frozenset({"Annotated", "Final", "ClassVar", "Required", "NotRequired", "ReadOnly", "Optional"})
|
|
42
|
-
_VALIDATORS = frozenset(
|
|
43
|
-
{
|
|
44
|
-
"pydantic.model_validator",
|
|
45
|
-
"pydantic.field_validator",
|
|
46
|
-
"pydantic.functional_validators.model_validator",
|
|
47
|
-
"pydantic.functional_validators.field_validator",
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
_DISCRIMINATORS = frozenset({"pydantic.Discriminator", "pydantic.types.Discriminator"})
|
|
51
|
-
_RAW_METADATA = frozenset(
|
|
52
|
-
{
|
|
53
|
-
"pydantic.BeforeValidator",
|
|
54
|
-
"pydantic.WrapValidator",
|
|
55
|
-
"pydantic.functional_validators.BeforeValidator",
|
|
56
|
-
"pydantic.functional_validators.WrapValidator",
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
43
|
_MAX_DEPTH = 32
|
|
60
44
|
_MAPPING_ARITY = 2
|
|
61
45
|
_BAD = (
|
|
@@ -102,8 +86,8 @@ class RequirePydanticForMappingShape(Rule):
|
|
|
102
86
|
limitations=(
|
|
103
87
|
"Checks literal-string get/subscript reads on parameters or local names annotated as standard string mappings with object, Any or pydantic.JsonValue values. Stable module type aliases, quoted annotations, wrappers and local value aliases are recognized with bounded resolution.",
|
|
104
88
|
"A field must feed builtin isinstance/type checks, literal comparisons, truth tests or a constrained match pattern. Ordinary reads/forwarding, dynamic keys, homogeneous typed maps, imported application aliases, attributes and ambiguous/rebound assignments are excluded.",
|
|
105
|
-
"Import-resolved Pydantic before/wrap
|
|
106
|
-
"
|
|
89
|
+
"Import-resolved Pydantic before/wrap decorators with literal or stable local mode aliases, metadata validators and registered Discriminator callbacks own raw normalization. Stable function aliases, functools.partial binding only keyword-only configuration and simple bound/static/class methods are recognized. Prefer declarative fields before introducing callbacks.",
|
|
90
|
+
"Callback classes must be stable direct local declarations with no bases, class options, custom constructor or descriptor hooks. Import-proven typing.final and empty __slots__ preserve ownership. Other decorators, imported/inherited classes and directly mutated callback owners abstain. Nonescaping direct local helpers inherit raw ownership; escaped helpers remain checked. Generic type parameters shadow matching builtin/import annotation names.",
|
|
107
91
|
"Parsing helpers in authored tests are checked, but assert-contained test oracles are excluded. Generated/vendor sources and dynamic namespaces are excluded. This is a warning with no automatic rewrite because coercion, missing-field handling and error policy can differ.",
|
|
108
92
|
),
|
|
109
93
|
examples=(
|
|
@@ -168,58 +152,20 @@ class _Facts:
|
|
|
168
152
|
node: ImportIndex.from_tree(ast.Module(body=[node], type_ignores=[]))
|
|
169
153
|
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
170
154
|
}
|
|
171
|
-
self.callbacks =
|
|
172
|
-
binding
|
|
173
|
-
for call in context.nodes(ast.Call)
|
|
174
|
-
if (qualified := self.qualified(call.func, call)) in _DISCRIMINATORS | _RAW_METADATA
|
|
175
|
-
if (argument := _callback_argument(call, qualified)) is not None
|
|
176
|
-
if (binding := self.callback(argument)) is not None
|
|
177
|
-
}
|
|
155
|
+
self.callbacks = RawMappingCallbacks(context, self.provenance, self.qualified, self.builtin, self.shadowed)
|
|
178
156
|
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
return None
|
|
182
|
-
if isinstance(node, ast.Call):
|
|
183
|
-
return self.callable_method(node, seen=seen)
|
|
184
|
-
if isinstance(node, ast.Attribute) and node.attr == "__call__":
|
|
185
|
-
return self.callable_method(node.value, seen=seen)
|
|
186
|
-
if not isinstance(node, ast.Name) or node.id in self.provenance.mutated | self.provenance.external_bindings:
|
|
187
|
-
return None
|
|
188
|
-
binding = self.provenance.binding(node.id, node)
|
|
189
|
-
if isinstance(binding, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
190
|
-
return binding if not binding.decorator_list else None
|
|
191
|
-
if not isinstance(binding, ast.Name) or binding in seen:
|
|
192
|
-
return None
|
|
193
|
-
value = self.provenance.assignment_value(binding, node)
|
|
194
|
-
return self.callback(value, seen=seen | {binding}) if value is not None else None
|
|
195
|
-
|
|
196
|
-
def callable_method(self, node: ast.expr, *, seen: frozenset[ast.AST]) -> ast.FunctionDef | None:
|
|
197
|
-
if len(seen) >= _MAX_DEPTH:
|
|
198
|
-
return None
|
|
199
|
-
if isinstance(node, ast.Name):
|
|
200
|
-
if node.id in self.provenance.mutated | self.provenance.external_bindings:
|
|
201
|
-
return None
|
|
202
|
-
binding = self.provenance.binding(node.id, node)
|
|
203
|
-
if not isinstance(binding, ast.Name) or binding in seen:
|
|
204
|
-
return None
|
|
205
|
-
value = self.provenance.assignment_value(binding, node)
|
|
206
|
-
return self.callable_method(value, seen=seen | {binding}) if value is not None else None
|
|
207
|
-
if not isinstance(node, ast.Call) or node.args or node.keywords or not isinstance(node.func, ast.Name):
|
|
208
|
-
return None
|
|
209
|
-
if node.func.id in self.provenance.mutated | self.provenance.external_bindings:
|
|
210
|
-
return None
|
|
211
|
-
binding = self.provenance.binding(node.func.id, node)
|
|
212
|
-
if not isinstance(binding, ast.ClassDef) or not isinstance(
|
|
213
|
-
self.context.parents.get(binding), ast.Module | ast.FunctionDef | ast.AsyncFunctionDef
|
|
214
|
-
):
|
|
215
|
-
return None
|
|
216
|
-
return _plain_callable_method(binding)
|
|
157
|
+
def shadowed(self, name: str, at: ast.AST) -> bool:
|
|
158
|
+
return _type_parameter_shadow(name, at, self.context)
|
|
217
159
|
|
|
218
160
|
def qualified(self, node: ast.expr, at: ast.AST) -> str | None:
|
|
219
161
|
root = node
|
|
220
162
|
while isinstance(root, ast.Attribute):
|
|
221
163
|
root = root.value
|
|
222
|
-
if
|
|
164
|
+
if (
|
|
165
|
+
not isinstance(root, ast.Name)
|
|
166
|
+
or root.id in self.provenance.mutated | self.provenance.external_bindings
|
|
167
|
+
or _type_parameter_shadow(root.id, at, self.context)
|
|
168
|
+
):
|
|
223
169
|
return None
|
|
224
170
|
binding = self.provenance.binding(root.id, at)
|
|
225
171
|
parent = self.context.parents.get(binding) if binding is not None else None
|
|
@@ -231,6 +177,7 @@ class _Facts:
|
|
|
231
177
|
self.provenance.builtin_namespace_untouched
|
|
232
178
|
and isinstance(node, ast.Name)
|
|
233
179
|
and node.id == name
|
|
180
|
+
and not _type_parameter_shadow(name, at, self.context)
|
|
234
181
|
and self.provenance.binding(name, at) is None
|
|
235
182
|
)
|
|
236
183
|
|
|
@@ -240,6 +187,8 @@ class _Facts:
|
|
|
240
187
|
parsed = self.semantics.parse(node)
|
|
241
188
|
if parsed is None:
|
|
242
189
|
return False
|
|
190
|
+
if isinstance(parsed, ast.Name) and _type_parameter_shadow(parsed.id, at, self.context):
|
|
191
|
+
return False
|
|
243
192
|
if isinstance(parsed, ast.Name) and parsed.id in self.semantics.aliases:
|
|
244
193
|
return self.alias_annotation(parsed, at, mapping=mapping, depth=depth)
|
|
245
194
|
if isinstance(parsed, ast.BinOp) and isinstance(parsed.op, ast.BitOr):
|
|
@@ -320,27 +269,34 @@ class _Facts:
|
|
|
320
269
|
return None
|
|
321
270
|
|
|
322
271
|
def raw_boundary(self, node: ast.AST) -> bool:
|
|
323
|
-
|
|
324
|
-
while current is not None:
|
|
325
|
-
if isinstance(current, ast.FunctionDef | ast.AsyncFunctionDef) and self.raw_validator(current):
|
|
326
|
-
return True
|
|
327
|
-
current = self.context.parents.get(current)
|
|
328
|
-
return False
|
|
272
|
+
return self.callbacks.owns(node)
|
|
329
273
|
|
|
330
|
-
|
|
331
|
-
|
|
274
|
+
|
|
275
|
+
def _type_parameter_shadow(name: str, at: ast.AST, context: PythonFileContext) -> bool:
|
|
276
|
+
outside: set[ast.AST] = set()
|
|
277
|
+
current = at
|
|
278
|
+
while (parent := context.parents.get(current)) is not None:
|
|
279
|
+
if (
|
|
280
|
+
isinstance(parent, ast.arguments)
|
|
281
|
+
and (current in parent.defaults or current in parent.kw_defaults)
|
|
282
|
+
and (owner := context.parents.get(parent)) is not None
|
|
283
|
+
):
|
|
284
|
+
outside.add(owner)
|
|
285
|
+
if (
|
|
286
|
+
isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.TypeAlias)
|
|
287
|
+
and parent not in outside
|
|
288
|
+
and not (
|
|
289
|
+
isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
|
|
290
|
+
and current in parent.decorator_list
|
|
291
|
+
)
|
|
292
|
+
and any(
|
|
293
|
+
isinstance(parameter, ast.TypeVar | ast.ParamSpec | ast.TypeVarTuple) and parameter.name == name
|
|
294
|
+
for parameter in parent.type_params
|
|
295
|
+
)
|
|
296
|
+
):
|
|
332
297
|
return True
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
continue
|
|
336
|
-
if any(
|
|
337
|
-
keyword.arg == "mode"
|
|
338
|
-
and isinstance(keyword.value, ast.Constant)
|
|
339
|
-
and keyword.value.value in {"before", "wrap"}
|
|
340
|
-
for keyword in decorator.keywords
|
|
341
|
-
):
|
|
342
|
-
return True
|
|
343
|
-
return False
|
|
298
|
+
current = parent
|
|
299
|
+
return False
|
|
344
300
|
|
|
345
301
|
|
|
346
302
|
def _string(node: ast.expr) -> bool:
|
|
@@ -368,40 +324,6 @@ def _get_receiver(node: ast.expr) -> ast.expr | None:
|
|
|
368
324
|
return node.func.value
|
|
369
325
|
|
|
370
326
|
|
|
371
|
-
def _callback_argument(call: ast.Call, qualified: str | None) -> ast.expr | None:
|
|
372
|
-
if call.args:
|
|
373
|
-
return call.args[0]
|
|
374
|
-
parameter = "discriminator" if qualified in _DISCRIMINATORS else "func"
|
|
375
|
-
return next((keyword.value for keyword in call.keywords if keyword.arg == parameter), None)
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
def _plain_callable_method(node: ast.ClassDef) -> ast.FunctionDef | None:
|
|
379
|
-
if node.bases or node.keywords or node.decorator_list or node.type_params:
|
|
380
|
-
return None
|
|
381
|
-
method: ast.FunctionDef | None = None
|
|
382
|
-
for statement in node.body:
|
|
383
|
-
match statement:
|
|
384
|
-
case ast.FunctionDef(name="__call__", decorator_list=[]) if method is None:
|
|
385
|
-
method = statement
|
|
386
|
-
case ast.Assign(value=ast.Constant(), targets=targets) if all(
|
|
387
|
-
isinstance(target, ast.Name) and _inert_callable_attribute(target.id) for target in targets
|
|
388
|
-
):
|
|
389
|
-
continue
|
|
390
|
-
case ast.AnnAssign(target=ast.Name(id=name), value=None | ast.Constant()) if _inert_callable_attribute(
|
|
391
|
-
name
|
|
392
|
-
):
|
|
393
|
-
continue
|
|
394
|
-
case ast.Pass() | ast.Expr(value=ast.Constant(value=str())):
|
|
395
|
-
continue
|
|
396
|
-
case _:
|
|
397
|
-
return None
|
|
398
|
-
return method
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def _inert_callable_attribute(name: str) -> bool:
|
|
402
|
-
return name == "__name__" or not (name.startswith("__") and name.endswith("__"))
|
|
403
|
-
|
|
404
|
-
|
|
405
327
|
def _probe_sources(node: ast.AST, facts: _Facts) -> Iterator[ast.expr]:
|
|
406
328
|
match node:
|
|
407
329
|
case ast.Call(func=func, args=[value, _], keywords=[]) if facts.builtin(func, "isinstance", node):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.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.56.0 → sarj_python_lint-1.57.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.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.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
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_runtime_bindings.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_store_writes.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.56.0 → sarj_python_lint-1.57.0}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|