sarj-python-lint 1.108.51__tar.gz → 1.109.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.108.51 → sarj_python_lint-1.109.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/__main__.py +6 -3
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix.py +155 -32
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_comments.py +14 -9
- sarj_python_lint-1.109.0/src/sarj_python_lint/_autofix_sql_projection.py +223 -0
- sarj_python_lint-1.108.51/src/sarj_python_lint/_autofix_sql_projection.py → sarj_python_lint-1.109.0/src/sarj_python_lint/_closed_sql_literal.py +3 -89
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_parallel.py +4 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_rule_descriptors.py +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_binding_inventory.py +8 -2
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_closed_helpers.py +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_declared_contracts.py +38 -3
- sarj_python_lint-1.109.0/src/sarj_python_lint/rules/_fixture_contracts.py +278 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_postgres_ctas.py +26 -2
- sarj_python_lint-1.109.0/src/sarj_python_lint/rules/_pytest_contract_parameters.py +539 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_names.py +14 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/excessive_commentary.py +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dynamic_builtins.py +1 -1
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +48 -7
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_test_comments.py +46 -2
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +5 -5
- sarj_python_lint-1.109.0/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +167 -0
- sarj_python_lint-1.109.0/src/sarj_python_lint/rules/prefer_typed_cli.py +585 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +2 -2
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/stepdown.py +145 -1
- sarj_python_lint-1.108.51/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -213
- sarj_python_lint-1.108.51/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -233
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/.gitignore +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/LICENSE +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/README.md +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_loguru.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_shared_mutable_default.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_sys_path_mutation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/suppression_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.109.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/
|
|
@@ -222,15 +222,18 @@ def check_source(
|
|
|
222
222
|
) -> list[Diagnostic]:
|
|
223
223
|
from sarj_python_lint._file_context import PythonFileContext # ruff: ignore[import-outside-top-level] -- source checks own native AST context construction.
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
return check_context(rules, PythonFileContext(p, source, session))
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def check_context(rules: list[Rule], context: PythonFileContext) -> list[Diagnostic]:
|
|
226
229
|
if rules and context.tree is None:
|
|
227
|
-
msg = f"selected Python source could not be parsed: {
|
|
230
|
+
msg = f"selected Python source could not be parsed: {context.path}"
|
|
228
231
|
raise ValueError(msg)
|
|
229
232
|
source_lines = context.source_lines
|
|
230
233
|
raw = [diagnostic for rule in rules for diagnostic in rule.check_context(context)]
|
|
231
234
|
return deduplicate_diagnostics(
|
|
232
235
|
[diagnostic for diagnostic in raw if not is_suppressed(source_lines, diagnostic.line, diagnostic.code)],
|
|
233
|
-
source=source,
|
|
236
|
+
source=context.source,
|
|
234
237
|
context=context,
|
|
235
238
|
)
|
|
236
239
|
|
|
@@ -26,7 +26,7 @@ from sarj_python_lint._autofix_exhaustiveness import ExhaustivenessFixes
|
|
|
26
26
|
from sarj_python_lint._autofix_parameters import ParameterFixes
|
|
27
27
|
from sarj_python_lint._autofix_repeated import RepeatedLiteralFixes
|
|
28
28
|
from sarj_python_lint._autofix_security import fixes as security_fixes
|
|
29
|
-
from sarj_python_lint._autofix_sql_projection import fixes as sql_projection_fixes
|
|
29
|
+
from sarj_python_lint._autofix_sql_projection import alias_fixes as sql_alias_fixes, fixes as sql_projection_fixes
|
|
30
30
|
from sarj_python_lint._autofix_tests import fixes as testing_fixes
|
|
31
31
|
from sarj_python_lint._file_context import PythonFileContext
|
|
32
32
|
from sarj_python_lint._fixes import apply_fixes as apply_native_fixes
|
|
@@ -41,6 +41,7 @@ from sarj_python_lint.rules import (
|
|
|
41
41
|
from sarj_python_lint.rules._ast_index import object_list
|
|
42
42
|
from sarj_python_lint.rules._closed_helpers import dynamic_namespace
|
|
43
43
|
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
44
|
+
from sarj_python_lint.rules.prefer_match_type_dispatch import body_terminates
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
if TYPE_CHECKING:
|
|
@@ -81,6 +82,7 @@ _WALRUS_CODES = frozenset({"SARJ081", "SARJ432"})
|
|
|
81
82
|
_JWT_POSITIONAL_PREFIX_ARGUMENTS = 3
|
|
82
83
|
_STREAM_MIN_BODY_LENGTH = 3
|
|
83
84
|
_CONSTANT_TRUE_DEPTH_LIMIT = 32
|
|
85
|
+
_INERT_DEFAULT_NODE_LIMIT: Final = 128
|
|
84
86
|
# Bound accepted revisions; each rule still owns its work and replacement size.
|
|
85
87
|
_MAX_FIX_PASSES: Final = 256
|
|
86
88
|
_PURE_STRING_TRANSFORMS: Final[dict[str, str]] = {
|
|
@@ -125,6 +127,9 @@ class _Source:
|
|
|
125
127
|
self.export_locations = frozenset(
|
|
126
128
|
(diagnostic.line, diagnostic.col) for diagnostic in diagnostics if diagnostic.code == "SARJ098"
|
|
127
129
|
)
|
|
130
|
+
self.test_comment_locations = frozenset(
|
|
131
|
+
(diagnostic.line, diagnostic.col) for diagnostic in diagnostics if diagnostic.code == "SARJ551"
|
|
132
|
+
)
|
|
128
133
|
|
|
129
134
|
@cached_property
|
|
130
135
|
def enums(self) -> EnumFixes:
|
|
@@ -140,7 +145,7 @@ class _Source:
|
|
|
140
145
|
|
|
141
146
|
@cached_property
|
|
142
147
|
def commentary(self) -> CommentFixes:
|
|
143
|
-
return CommentFixes(self.context)
|
|
148
|
+
return CommentFixes(self.context, test_comment_locations=self.test_comment_locations)
|
|
144
149
|
|
|
145
150
|
@cached_property
|
|
146
151
|
def declared_contracts(self) -> DeclaredContractFixes:
|
|
@@ -460,6 +465,10 @@ def _sql_projection_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdi
|
|
|
460
465
|
return sql_projection_fixes(source.context, diagnostic)
|
|
461
466
|
|
|
462
467
|
|
|
468
|
+
def _sql_alias_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
469
|
+
return sql_alias_fixes(source.context, diagnostic)
|
|
470
|
+
|
|
471
|
+
|
|
463
472
|
def _alias_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
464
473
|
if not diagnostic.fixes:
|
|
465
474
|
return source.aliases.fixes(diagnostic)
|
|
@@ -767,9 +776,15 @@ def _dispatch_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
|
767
776
|
if diagnostic.code == "SARJ080" and diagnostic.severity is not Severity.ERROR:
|
|
768
777
|
return []
|
|
769
778
|
for root in source.context.nodes(ast.If):
|
|
770
|
-
if not _at(root, diagnostic)
|
|
779
|
+
if not _at(root, diagnostic):
|
|
771
780
|
continue
|
|
772
|
-
|
|
781
|
+
type_dispatch = diagnostic.code == "SARJ080"
|
|
782
|
+
roots = _dispatch_roots(root, source.context) if type_dispatch else [root]
|
|
783
|
+
if source.comments_between(root.lineno, _last_line(roots[-1])) or (
|
|
784
|
+
len(roots) > 1 and any(source.full_statement(branch) is None for branch in roots)
|
|
785
|
+
):
|
|
786
|
+
continue
|
|
787
|
+
matched = _dispatch_match(roots, source.context, type_dispatch=type_dispatch)
|
|
773
788
|
if matched is None or not isinstance(matched.subject, ast.Name):
|
|
774
789
|
continue
|
|
775
790
|
if not _dispatch_parameter(root, matched.subject, source):
|
|
@@ -777,15 +792,41 @@ def _dispatch_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
|
777
792
|
rendered = ast.unparse(matched)
|
|
778
793
|
indent = source.lines[root.lineno - 1][: root.col_offset]
|
|
779
794
|
newline = "\r\n" if "\r\n" in source.text else "\n"
|
|
780
|
-
return [
|
|
795
|
+
return [
|
|
796
|
+
TextEdit(
|
|
797
|
+
source.span(root).start, source.span(roots[-1]).end, (newline + indent).join(rendered.splitlines())
|
|
798
|
+
)
|
|
799
|
+
]
|
|
781
800
|
return []
|
|
782
801
|
|
|
783
802
|
|
|
784
|
-
def
|
|
803
|
+
def _dispatch_roots(root: ast.If, context: PythonFileContext) -> list[ast.If]:
|
|
804
|
+
if root.orelse:
|
|
805
|
+
return [root]
|
|
806
|
+
parent = context.parents.get(root)
|
|
807
|
+
for field in ("body", "orelse", "finalbody"):
|
|
808
|
+
value: object = getattr( # sarj-noqa: SARJ557 -- Inspect optional statement-list fields across heterogeneous AST node types.
|
|
809
|
+
parent, field, None
|
|
810
|
+
)
|
|
811
|
+
if not object_list(value) or root not in value:
|
|
812
|
+
continue
|
|
813
|
+
branches: list[ast.If] = []
|
|
814
|
+
for statement in value[value.index(root) :]:
|
|
815
|
+
if isinstance(statement, ast.If) and statement.orelse:
|
|
816
|
+
return [root]
|
|
817
|
+
if not isinstance(statement, ast.If) or not body_terminates(statement.body):
|
|
818
|
+
break
|
|
819
|
+
branches.append(statement)
|
|
820
|
+
return branches or [root]
|
|
821
|
+
return [root]
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _dispatch_match(roots: Sequence[ast.If], context: PythonFileContext, *, type_dispatch: bool) -> ast.Match | None:
|
|
785
825
|
cases: list[ast.match_case] = []
|
|
786
|
-
|
|
826
|
+
pending = list(reversed(roots))
|
|
787
827
|
subject: ast.Name | None = None
|
|
788
|
-
while
|
|
828
|
+
while pending:
|
|
829
|
+
branch = pending.pop()
|
|
789
830
|
if type_dispatch and _type_parameter_reference(branch.test, context):
|
|
790
831
|
return None
|
|
791
832
|
pattern = _dispatch_pattern(branch.test, type_dispatch=type_dispatch)
|
|
@@ -794,11 +835,12 @@ def _dispatch_match(root: ast.If, context: PythonFileContext, *, type_dispatch:
|
|
|
794
835
|
subject = pattern[0]
|
|
795
836
|
cases.append(ast.match_case(pattern=pattern[1], guard=None, body=branch.body))
|
|
796
837
|
if len(branch.orelse) == 1 and isinstance(branch.orelse[0], ast.If):
|
|
797
|
-
branch
|
|
798
|
-
|
|
799
|
-
if branch.orelse:
|
|
838
|
+
pending.append(branch.orelse[0])
|
|
839
|
+
elif branch.orelse:
|
|
800
840
|
cases.append(ast.match_case(pattern=ast.MatchAs(), guard=None, body=branch.orelse))
|
|
801
|
-
|
|
841
|
+
if subject is None or (type_dispatch and len(cases) == 1):
|
|
842
|
+
return None
|
|
843
|
+
return ast.Match(subject=subject, cases=cases)
|
|
802
844
|
|
|
803
845
|
|
|
804
846
|
def _dispatch_pattern(test: ast.expr, *, type_dispatch: bool) -> tuple[ast.Name, ast.pattern] | None:
|
|
@@ -1233,32 +1275,79 @@ def _literal_collection_element(element: ast.expr, names: frozenset[str]) -> boo
|
|
|
1233
1275
|
return isinstance(element, ast.Constant) or (isinstance(element, ast.Name) and element.id in names)
|
|
1234
1276
|
|
|
1235
1277
|
|
|
1236
|
-
def _inert_definition(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
1278
|
+
def _inert_definition(node: ast.FunctionDef | ast.AsyncFunctionDef, *, postponed_annotations: bool) -> bool:
|
|
1237
1279
|
args = node.args
|
|
1238
1280
|
if node.decorator_list or node.type_params:
|
|
1239
1281
|
return False
|
|
1240
1282
|
return not (
|
|
1241
|
-
args.defaults
|
|
1242
|
-
or any(value is not None for value in args.kw_defaults)
|
|
1243
|
-
or
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1283
|
+
any(not _inert_default(value) for value in args.defaults)
|
|
1284
|
+
or any(value is not None and not _inert_default(value) for value in args.kw_defaults)
|
|
1285
|
+
or (
|
|
1286
|
+
not postponed_annotations
|
|
1287
|
+
and (
|
|
1288
|
+
not _inert_annotation(node.returns)
|
|
1289
|
+
or any(
|
|
1290
|
+
not _inert_annotation(argument.annotation)
|
|
1291
|
+
for argument in (*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg)
|
|
1292
|
+
if argument is not None
|
|
1293
|
+
)
|
|
1294
|
+
)
|
|
1248
1295
|
)
|
|
1249
1296
|
)
|
|
1250
1297
|
|
|
1251
1298
|
|
|
1299
|
+
def _inert_default(value: ast.expr) -> bool:
|
|
1300
|
+
if isinstance(value, ast.Constant):
|
|
1301
|
+
return True
|
|
1302
|
+
pending: list[tuple[ast.expr, bool]] = [(value, False)]
|
|
1303
|
+
remaining = _INERT_DEFAULT_NODE_LIMIT
|
|
1304
|
+
while pending:
|
|
1305
|
+
if not remaining:
|
|
1306
|
+
return False
|
|
1307
|
+
expression, numeric_required = pending.pop()
|
|
1308
|
+
remaining -= 1
|
|
1309
|
+
match expression:
|
|
1310
|
+
case ast.Constant(value=literal):
|
|
1311
|
+
literal_type = type(literal)
|
|
1312
|
+
if (
|
|
1313
|
+
numeric_required
|
|
1314
|
+
and literal_type is not int
|
|
1315
|
+
and literal_type is not float
|
|
1316
|
+
and literal_type is not complex
|
|
1317
|
+
):
|
|
1318
|
+
return False
|
|
1319
|
+
continue
|
|
1320
|
+
case ast.Tuple(elts=elements) if not numeric_required:
|
|
1321
|
+
children: Sequence[ast.expr] = elements
|
|
1322
|
+
child_numeric_required = False
|
|
1323
|
+
case ast.UnaryOp(op=ast.UAdd() | ast.USub(), operand=operand):
|
|
1324
|
+
children = (operand,)
|
|
1325
|
+
child_numeric_required = True
|
|
1326
|
+
case _:
|
|
1327
|
+
return False
|
|
1328
|
+
if len(children) + len(pending) > remaining:
|
|
1329
|
+
return False
|
|
1330
|
+
pending.extend((child, child_numeric_required) for child in children)
|
|
1331
|
+
return True
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
def _inert_annotation(annotation: ast.expr | None) -> bool:
|
|
1335
|
+
return annotation is None or (
|
|
1336
|
+
isinstance(annotation, ast.Constant) and (annotation.value is None or isinstance(annotation.value, str))
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
|
|
1252
1340
|
def _stepdown_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
1253
1341
|
tree = source.context.tree
|
|
1254
1342
|
if tree is None:
|
|
1255
1343
|
return []
|
|
1344
|
+
postponed_annotations = _postponed_module_annotations(tree) or bool(source.context.nodes(ast.TemplateStr))
|
|
1256
1345
|
for index, helper in enumerate(tree.body):
|
|
1257
1346
|
if not isinstance(helper, (ast.FunctionDef, ast.AsyncFunctionDef)) or not _at(helper, diagnostic):
|
|
1258
1347
|
continue
|
|
1259
|
-
if not _inert_definition(helper):
|
|
1348
|
+
if not _inert_definition(helper, postponed_annotations=postponed_annotations):
|
|
1260
1349
|
continue
|
|
1261
|
-
caller = _inert_stepdown_caller(helper, tree.body[index + 1 :])
|
|
1350
|
+
caller = _inert_stepdown_caller(helper, tree.body[index + 1 :], postponed_annotations=postponed_annotations)
|
|
1262
1351
|
if caller is None or _stepdown_comments(helper, caller, source):
|
|
1263
1352
|
continue
|
|
1264
1353
|
span = source.full_statement(helper)
|
|
@@ -1270,20 +1359,37 @@ def _stepdown_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
|
1270
1359
|
return []
|
|
1271
1360
|
|
|
1272
1361
|
|
|
1362
|
+
def _postponed_module_annotations(tree: ast.Module) -> bool:
|
|
1363
|
+
for index, statement in enumerate(tree.body):
|
|
1364
|
+
if (
|
|
1365
|
+
index == 0
|
|
1366
|
+
and isinstance(statement, ast.Expr)
|
|
1367
|
+
and isinstance(statement.value, ast.Constant)
|
|
1368
|
+
and isinstance(statement.value.value, str)
|
|
1369
|
+
):
|
|
1370
|
+
continue
|
|
1371
|
+
if not isinstance(statement, ast.ImportFrom) or statement.module != "__future__" or statement.level != 0:
|
|
1372
|
+
return False
|
|
1373
|
+
if any(alias.name == "annotations" for alias in statement.names):
|
|
1374
|
+
return True
|
|
1375
|
+
return False
|
|
1376
|
+
|
|
1377
|
+
|
|
1273
1378
|
def _inert_stepdown_caller(
|
|
1274
|
-
helper: ast.FunctionDef | ast.AsyncFunctionDef, later: list[ast.stmt]
|
|
1379
|
+
helper: ast.FunctionDef | ast.AsyncFunctionDef, later: list[ast.stmt], *, postponed_annotations: bool
|
|
1275
1380
|
) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
|
|
1276
1381
|
callers = [
|
|
1277
1382
|
node
|
|
1278
1383
|
for node in later
|
|
1279
1384
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and _calls_function(node, helper.name)
|
|
1280
1385
|
]
|
|
1281
|
-
if len(callers) != 1 or not _inert_definition(callers[0]):
|
|
1386
|
+
if len(callers) != 1 or not _inert_definition(callers[0], postponed_annotations=postponed_annotations):
|
|
1282
1387
|
return None
|
|
1283
1388
|
caller = callers[0]
|
|
1284
1389
|
for node in later:
|
|
1285
1390
|
if node.lineno <= caller.lineno and (
|
|
1286
|
-
not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
1391
|
+
not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
1392
|
+
or not _inert_definition(node, postponed_annotations=postponed_annotations)
|
|
1287
1393
|
):
|
|
1288
1394
|
return None
|
|
1289
1395
|
return caller
|
|
@@ -1322,6 +1428,7 @@ def _parameter_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
|
1322
1428
|
|
|
1323
1429
|
_BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
|
|
1324
1430
|
"SARJ021": _sql_projection_fix,
|
|
1431
|
+
"SARJ543": _sql_alias_fix,
|
|
1325
1432
|
"SARJ002": _literal_loop_fix,
|
|
1326
1433
|
"SARJ006": _enum_fix,
|
|
1327
1434
|
"SARJ011": _security_fix,
|
|
@@ -1332,7 +1439,7 @@ _BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
|
|
|
1332
1439
|
"SARJ024": _repeated_fix,
|
|
1333
1440
|
"SARJ032": _exhaustiveness_fix,
|
|
1334
1441
|
"SARJ046": _xfail_fix,
|
|
1335
|
-
**dict.fromkeys({"SARJ049", "SARJ051", "SARJ054", "SARJ434"}, _commentary_fix),
|
|
1442
|
+
**dict.fromkeys({"SARJ049", "SARJ051", "SARJ054", "SARJ434", "SARJ551"}, _commentary_fix),
|
|
1336
1443
|
"SARJ067": _mock_fix,
|
|
1337
1444
|
"SARJ068": _fstring_fix,
|
|
1338
1445
|
"SARJ070": _pattern_fix,
|
|
@@ -1378,7 +1485,7 @@ class _FixPass:
|
|
|
1378
1485
|
|
|
1379
1486
|
|
|
1380
1487
|
def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None) -> str:
|
|
1381
|
-
from sarj_python_lint.__main__ import
|
|
1488
|
+
from sarj_python_lint.__main__ import check_context # ruff: ignore[import-outside-top-level] -- the CLI imports this entry point lazily.
|
|
1382
1489
|
|
|
1383
1490
|
selected = list(REGISTRY) if rule_ids is None else list(rule_ids)
|
|
1384
1491
|
rules = [REGISTRY[rule_id]() for rule_id in selected if REGISTRY[rule_id].code in _BUILDERS]
|
|
@@ -1394,16 +1501,13 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
|
|
|
1394
1501
|
while (fingerprint := sha256(current.encode("utf-8")).digest()) not in seen:
|
|
1395
1502
|
seen.add(fingerprint)
|
|
1396
1503
|
context = PythonFileContext(path, current)
|
|
1397
|
-
diagnostics =
|
|
1504
|
+
diagnostics = check_context(rules, context)
|
|
1398
1505
|
candidates = _candidate_edits(context, diagnostics)
|
|
1399
1506
|
if not candidates:
|
|
1400
1507
|
return current
|
|
1401
1508
|
# Batch independent candidates; overlapping candidates get fresh offsets on the next pass.
|
|
1402
1509
|
selected_pass = _coherent_edits(candidates)
|
|
1403
|
-
updated =
|
|
1404
|
-
compile( # sarj-noqa: SARJ557 -- Compile proposed fixes to reject invalid scopes without executing them.
|
|
1405
|
-
updated, str(path), "exec", dont_inherit=True
|
|
1406
|
-
)
|
|
1510
|
+
updated = _apply_validated_edits(path, current, selected_pass.edits)
|
|
1407
1511
|
if updated == current:
|
|
1408
1512
|
return current
|
|
1409
1513
|
if passes == _MAX_FIX_PASSES:
|
|
@@ -1414,6 +1518,23 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
|
|
|
1414
1518
|
raise _convergence_error(path, reason="source cycle", passes=passes, codes=applied_codes)
|
|
1415
1519
|
|
|
1416
1520
|
|
|
1521
|
+
def _apply_validated_edits(path: Path, current: str, edits: Sequence[TextEdit]) -> str:
|
|
1522
|
+
updated = apply_edits(current, edits)
|
|
1523
|
+
try:
|
|
1524
|
+
compile( # sarj-noqa: SARJ557 -- Compile proposed fixes to reject invalid scopes without executing them.
|
|
1525
|
+
updated, str(path), "exec", dont_inherit=True
|
|
1526
|
+
)
|
|
1527
|
+
except SyntaxError:
|
|
1528
|
+
try:
|
|
1529
|
+
compile( # sarj-noqa: SARJ557 -- Distinguish pre-existing invalid scope from a broken generated fix without executing input.
|
|
1530
|
+
current, str(path), "exec", dont_inherit=True
|
|
1531
|
+
)
|
|
1532
|
+
except SyntaxError:
|
|
1533
|
+
return current
|
|
1534
|
+
raise
|
|
1535
|
+
return updated
|
|
1536
|
+
|
|
1537
|
+
|
|
1417
1538
|
def _convergence_error(
|
|
1418
1539
|
path: Path, *, reason: Literal["pass budget exhausted", "source cycle"], passes: int, codes: set[str]
|
|
1419
1540
|
) -> ValueError:
|
|
@@ -1423,6 +1544,8 @@ def _convergence_error(
|
|
|
1423
1544
|
|
|
1424
1545
|
|
|
1425
1546
|
def _candidate_edits(context: PythonFileContext, diagnostics: Sequence[Diagnostic]) -> list[_FixCandidate]:
|
|
1547
|
+
if not diagnostics:
|
|
1548
|
+
return []
|
|
1426
1549
|
view = _Source(context, diagnostics)
|
|
1427
1550
|
return [
|
|
1428
1551
|
_FixCandidate(diagnostic.code, tuple(edits))
|
{sarj_python_lint-1.108.51 → sarj_python_lint-1.109.0}/src/sarj_python_lint/_autofix_comments.py
RENAMED
|
@@ -24,13 +24,18 @@ _UNUSED_API_DIRECTIVE = re.compile(
|
|
|
24
24
|
r"#\s*ruff:\s*(?:noqa\s*:\s*TID251|file-ignore\s*\[\s*(?:TID251|banned-api)\s*\])\s*\Z",
|
|
25
25
|
re.IGNORECASE,
|
|
26
26
|
)
|
|
27
|
-
_MIN_PARAGRAPH_LINES = 4
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
@final
|
|
31
30
|
class CommentFixes:
|
|
32
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
context: PythonFileContext,
|
|
34
|
+
*,
|
|
35
|
+
test_comment_locations: frozenset[tuple[int, int]] = frozenset(),
|
|
36
|
+
) -> None:
|
|
33
37
|
self.context = context
|
|
38
|
+
self.test_comment_locations = test_comment_locations
|
|
34
39
|
self.lines = StringIO(context.source, newline="").readlines()
|
|
35
40
|
self.starts = [0]
|
|
36
41
|
for line in self.lines:
|
|
@@ -57,8 +62,12 @@ class CommentFixes:
|
|
|
57
62
|
if diagnostic.code == "SARJ054" and self._unused_api_directive(token):
|
|
58
63
|
return [self._remove(token)]
|
|
59
64
|
if diagnostic.code == "SARJ434" and diagnostic.severity is Severity.ERROR:
|
|
60
|
-
paragraph = self.
|
|
65
|
+
paragraph = self._restates_narration(token)
|
|
61
66
|
return [self._remove(comment) for comment in paragraph]
|
|
67
|
+
if diagnostic.code == "SARJ551":
|
|
68
|
+
paragraph = self._restates_narration(token)
|
|
69
|
+
if all((comment.start[0], comment.start[1] + 1) in self.test_comment_locations for comment in paragraph):
|
|
70
|
+
return [self._remove(comment) for comment in paragraph]
|
|
62
71
|
return []
|
|
63
72
|
|
|
64
73
|
def _unused_api_directive(self, comment: tokenize.TokenInfo) -> bool:
|
|
@@ -114,18 +123,14 @@ class CommentFixes:
|
|
|
114
123
|
case _:
|
|
115
124
|
return False
|
|
116
125
|
|
|
117
|
-
def
|
|
126
|
+
def _restates_narration(self, first: tokenize.TokenInfo) -> list[tokenize.TokenInfo]:
|
|
118
127
|
group = self.prose_groups.get((first.start[0], first.start[1] + 1))
|
|
119
128
|
if group is None:
|
|
120
129
|
return []
|
|
121
130
|
if self.narration is None:
|
|
122
131
|
self.narration = CommentNarration(self.context)
|
|
123
132
|
matched = self.narration.match(group)
|
|
124
|
-
if
|
|
125
|
-
matched is not None
|
|
126
|
-
and matched.kind == "accumulation"
|
|
127
|
-
and len(matched.comment_lines) >= _MIN_PARAGRAPH_LINES
|
|
128
|
-
):
|
|
133
|
+
if matched is not None:
|
|
129
134
|
return [self.comments[line] for line in matched.comment_lines]
|
|
130
135
|
return []
|
|
131
136
|
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from io import StringIO
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from sarj_rule_contracts import TextEdit, apply_edits
|
|
9
|
+
from sqlglot import exp, parse_one
|
|
10
|
+
from sqlglot.errors import ParseError, TokenError
|
|
11
|
+
|
|
12
|
+
from sarj_python_lint._closed_sql_literal import (
|
|
13
|
+
has_comments as _has_comments,
|
|
14
|
+
literal_query as _literal_query,
|
|
15
|
+
owned_literal as _owned_literal,
|
|
16
|
+
)
|
|
17
|
+
from sarj_python_lint.rules._ast_index import object_list
|
|
18
|
+
from sarj_python_lint.rules._store_sql import sql_child, sql_children
|
|
19
|
+
from sarj_python_lint.rules.no_redundant_sql_alias import identifier_key
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
import ast
|
|
24
|
+
|
|
25
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
26
|
+
from sarj_python_lint.rule_base import Diagnostic
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_QUERY = re.compile(
|
|
30
|
+
r"[ \t]*SELECT[ \t]+(?P<star>\*)[ \t]+FROM[ \t]+"
|
|
31
|
+
r"\([ \t]*VALUES[ \t]+\([ \t]*(?P<values>[0-9]{1,9}(?:[ \t]*,[ \t]*[0-9]{1,9}){0,7})"
|
|
32
|
+
r'[ \t]*\)[ \t]*\)[ \t]+AS[ \t]+(?P<alias>"[A-Za-z_][A-Za-z0-9_]{0,62}")[ \t]*'
|
|
33
|
+
r'\([ \t]*(?P<labels>"[A-Za-z_][A-Za-z0-9_]{0,62}"'
|
|
34
|
+
r'(?:[ \t]*,[ \t]*"[A-Za-z_][A-Za-z0-9_]{0,62}"){0,7})[ \t]*\)'
|
|
35
|
+
r"[ \t]*;?[ \t]*\Z",
|
|
36
|
+
re.IGNORECASE | re.ASCII,
|
|
37
|
+
)
|
|
38
|
+
_QUERY_LIMIT = 2048
|
|
39
|
+
_SQL_IDENTIFIER = r'(?:"[A-Za-z_][A-Za-z0-9_]{0,62}"|[A-Za-z_][A-Za-z0-9_]{0,62})'
|
|
40
|
+
_INTEGER_LITERAL = re.compile(r"[0-9]{1,9}\Z", re.ASCII)
|
|
41
|
+
_VALUES_ROW = r"\([ \t]*[0-9]{1,9}(?:[ \t]*,[ \t]*[0-9]{1,9}){0,7}[ \t]*\)"
|
|
42
|
+
_VALUES_BOUND = 8
|
|
43
|
+
_SELF_ALIAS_COLUMN = re.compile(
|
|
44
|
+
rf"(?P<column>{_SQL_IDENTIFIER}\.{_SQL_IDENTIFIER})(?P<alias>[ \t]+AS[ \t]+{_SQL_IDENTIFIER})?",
|
|
45
|
+
re.IGNORECASE | re.ASCII,
|
|
46
|
+
)
|
|
47
|
+
_SELF_ALIAS_QUERY = re.compile(
|
|
48
|
+
rf"[ \t]*SELECT[ \t]+(?P<columns>{_SELF_ALIAS_COLUMN.pattern}"
|
|
49
|
+
rf"(?:[ \t]*,[ \t]*{_SQL_IDENTIFIER}\.{_SQL_IDENTIFIER}(?:[ \t]+AS[ \t]+{_SQL_IDENTIFIER})?){{0,7}})"
|
|
50
|
+
r"[ \t]+FROM[ \t]+\([ \t]*VALUES[ \t]+"
|
|
51
|
+
rf"{_VALUES_ROW}(?:[ \t]*,[ \t]*{_VALUES_ROW}){{0,7}}[ \t]*\)"
|
|
52
|
+
rf"[ \t]+AS[ \t]+{_SQL_IDENTIFIER}[ \t]*\([ \t]*"
|
|
53
|
+
rf"{_SQL_IDENTIFIER}(?:[ \t]*,[ \t]*{_SQL_IDENTIFIER}){{0,7}}[ \t]*\)[ \t]*;?[ \t]*\Z",
|
|
54
|
+
re.IGNORECASE | re.ASCII,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class _Projection:
|
|
60
|
+
offset: int
|
|
61
|
+
replacement: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def fixes(context: PythonFileContext, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
65
|
+
tree = context.tree
|
|
66
|
+
if tree is None or context.generated or _has_comments(context.source):
|
|
67
|
+
return []
|
|
68
|
+
literal = _owned_literal(tree)
|
|
69
|
+
if literal is None or (literal.lineno, literal.col_offset + 1) != (diagnostic.line, diagnostic.col):
|
|
70
|
+
return []
|
|
71
|
+
query = _literal_query(context.source, literal)
|
|
72
|
+
projection = _projection(query) if query is not None else None
|
|
73
|
+
if projection is None:
|
|
74
|
+
return []
|
|
75
|
+
start = _literal_offset(context.source, literal) + projection.offset
|
|
76
|
+
return [TextEdit(start, start + 1, projection.replacement)]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def alias_fixes(context: PythonFileContext, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
80
|
+
tree = context.tree
|
|
81
|
+
if tree is None or context.generated or _has_comments(context.source):
|
|
82
|
+
return []
|
|
83
|
+
literal = _owned_literal(tree)
|
|
84
|
+
if literal is None or (literal.lineno, literal.col_offset + 1) != (diagnostic.line, diagnostic.col):
|
|
85
|
+
return []
|
|
86
|
+
query = _literal_query(context.source, literal)
|
|
87
|
+
if query is None:
|
|
88
|
+
return []
|
|
89
|
+
start = _literal_offset(context.source, literal)
|
|
90
|
+
return [TextEdit(start + edit.start, start + edit.end, edit.replacement) for edit in _alias_edits(query)]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _literal_offset(source: str, literal: ast.Constant) -> int:
|
|
94
|
+
lines = StringIO(source, newline="").readlines()
|
|
95
|
+
prefix = lines[literal.lineno - 1].encode("utf-8")[: literal.col_offset].decode("utf-8")
|
|
96
|
+
return sum(map(len, lines[: literal.lineno - 1])) + len(prefix) + 1
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _alias_edits(query: str) -> list[TextEdit]:
|
|
100
|
+
if len(query) > _QUERY_LIMIT or (matched := _SELF_ALIAS_QUERY.fullmatch(query)) is None:
|
|
101
|
+
return []
|
|
102
|
+
offset = matched.start("columns")
|
|
103
|
+
edits = [
|
|
104
|
+
TextEdit(offset + part.start("alias"), offset + part.end("alias"), "")
|
|
105
|
+
for part in _SELF_ALIAS_COLUMN.finditer(matched["columns"])
|
|
106
|
+
if part.group("alias") is not None
|
|
107
|
+
]
|
|
108
|
+
if not edits:
|
|
109
|
+
return []
|
|
110
|
+
candidate = apply_edits(query, edits)
|
|
111
|
+
return edits if _alias_delta(query, candidate) else []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _alias_delta(original: str, candidate: str) -> bool:
|
|
115
|
+
try:
|
|
116
|
+
before = parse_one(original, read="postgres")
|
|
117
|
+
after = parse_one(candidate, read="postgres")
|
|
118
|
+
except ParseError, TokenError:
|
|
119
|
+
return False
|
|
120
|
+
if not isinstance(before, exp.Select) or not isinstance(after, exp.Select):
|
|
121
|
+
return False
|
|
122
|
+
columns = _self_alias_columns(before)
|
|
123
|
+
if columns is None:
|
|
124
|
+
return False
|
|
125
|
+
expected = before.copy()
|
|
126
|
+
expected.set("expressions", [part.copy() for part in columns])
|
|
127
|
+
return expected == after
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _self_alias_columns(statement: exp.Select) -> list[exp.Column] | None:
|
|
131
|
+
from_clause = sql_child(statement, "from_")
|
|
132
|
+
relation = sql_child(from_clause, "this") if isinstance(from_clause, exp.From) else None
|
|
133
|
+
alias = sql_child(relation, "alias") if isinstance(relation, exp.Values) else None
|
|
134
|
+
if not isinstance(alias, exp.TableAlias):
|
|
135
|
+
return None
|
|
136
|
+
qualifier = identifier_key(sql_child(alias, "this"))
|
|
137
|
+
labels = [identifier_key(part) for part in sql_children(alias, "columns")]
|
|
138
|
+
if (
|
|
139
|
+
qualifier is None
|
|
140
|
+
or None in labels
|
|
141
|
+
or len(set(labels)) != len(labels)
|
|
142
|
+
or not isinstance(relation, exp.Values)
|
|
143
|
+
or not _literal_rows(relation, len(labels))
|
|
144
|
+
):
|
|
145
|
+
return None
|
|
146
|
+
columns: list[exp.Column] = []
|
|
147
|
+
for expression in sql_children(statement, "expressions"):
|
|
148
|
+
column = _self_alias_column(expression)
|
|
149
|
+
if (
|
|
150
|
+
column is None
|
|
151
|
+
or identifier_key(sql_child(column, "table")) != qualifier
|
|
152
|
+
or identifier_key(sql_child(column, "this")) not in labels
|
|
153
|
+
):
|
|
154
|
+
return None
|
|
155
|
+
columns.append(column)
|
|
156
|
+
return columns
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _literal_rows(relation: exp.Values, width: int) -> bool:
|
|
160
|
+
rows = sql_children(relation, "expressions")
|
|
161
|
+
return (
|
|
162
|
+
1 <= width <= _VALUES_BOUND
|
|
163
|
+
and 1 <= len(rows) <= _VALUES_BOUND
|
|
164
|
+
and all(isinstance(row, exp.Tuple) and _literal_row(row, width) for row in rows)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _literal_row(row: exp.Tuple, width: int) -> bool:
|
|
169
|
+
values = sql_children(row, "expressions")
|
|
170
|
+
return len(values) == width and all(
|
|
171
|
+
isinstance(value, exp.Literal) and not value.is_string and _INTEGER_LITERAL.fullmatch(value.name) is not None
|
|
172
|
+
for value in values
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _self_alias_column(expression: exp.Expr) -> exp.Column | None:
|
|
177
|
+
if isinstance(expression, exp.Column):
|
|
178
|
+
return expression
|
|
179
|
+
column = sql_child(expression, "this") if isinstance(expression, exp.Alias) else None
|
|
180
|
+
if not isinstance(column, exp.Column):
|
|
181
|
+
return None
|
|
182
|
+
original = identifier_key(sql_child(column, "this"))
|
|
183
|
+
return column if original is not None and original == identifier_key(sql_child(expression, "alias")) else None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _projection(query: str) -> _Projection | None:
|
|
187
|
+
if len(query) > _QUERY_LIMIT or (matched := _QUERY.fullmatch(query)) is None:
|
|
188
|
+
return None
|
|
189
|
+
labels = [part.strip() for part in matched["labels"].split(",")]
|
|
190
|
+
if len(labels) != len(matched["values"].split(",")) or len(set(labels)) != len(labels):
|
|
191
|
+
return None
|
|
192
|
+
replacement = ", ".join(f"{matched['alias']}.{label}" for label in labels)
|
|
193
|
+
candidate = query[: matched.start("star")] + replacement + query[matched.end("star") :]
|
|
194
|
+
if not _projection_delta(query, candidate):
|
|
195
|
+
return None
|
|
196
|
+
return _Projection(offset=matched.start("star"), replacement=replacement)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _projection_delta(original: str, candidate: str) -> bool:
|
|
200
|
+
try:
|
|
201
|
+
before = parse_one(original, read="postgres")
|
|
202
|
+
after = parse_one(candidate, read="postgres")
|
|
203
|
+
except ParseError, TokenError:
|
|
204
|
+
return False
|
|
205
|
+
if not isinstance(before, exp.Select) or not isinstance(after, exp.Select) or before.expressions != [exp.Star()]:
|
|
206
|
+
return False
|
|
207
|
+
columns = _projection_columns(after.expressions)
|
|
208
|
+
if columns is None:
|
|
209
|
+
return False
|
|
210
|
+
expected = before.copy()
|
|
211
|
+
expected.set("expressions", [part.copy() for part in columns])
|
|
212
|
+
return expected == after
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _projection_columns(value: object) -> list[exp.Column] | None:
|
|
216
|
+
if not object_list(value):
|
|
217
|
+
return None
|
|
218
|
+
columns: list[exp.Column] = []
|
|
219
|
+
for part in value:
|
|
220
|
+
if not isinstance(part, exp.Column):
|
|
221
|
+
return None
|
|
222
|
+
columns.append(part)
|
|
223
|
+
return columns
|