sarj-python-lint 1.109.0__tar.gz → 1.110.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.109.0 → sarj_python_lint-1.110.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/__main__.py +1 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_rule_descriptors.py +28 -1
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_registry.py +6 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_store_sql.py +109 -4
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +50 -13
- sarj_python_lint-1.110.0/src/sarj_python_lint/rules/no_psycopg_execution_in_loop.py +121 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +19 -2
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +95 -0
- sarj_python_lint-1.110.0/src/sarj_python_lint/rules/prefer_single_psycopg_cursor.py +118 -0
- sarj_python_lint-1.110.0/src/sarj_python_lint/rules/prefer_validated_uuid_comparison.py +97 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +86 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/.gitignore +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/LICENSE +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/README.md +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_closed_sql_literal.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_fixture_contracts.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_loguru.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_postgres_ctas.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_pytest_contract_parameters.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_runtime_names.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_dynamic_builtins.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_shared_mutable_default.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_sys_path_mutation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.109.0 → sarj_python_lint-1.110.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.110.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/
|
{sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/_rule_descriptors.py
RENAMED
|
@@ -8,6 +8,33 @@ from sarj_python_lint.rule_metadata import RuleDescriptor
|
|
|
8
8
|
|
|
9
9
|
SCHEMA_VERSION = 1
|
|
10
10
|
DESCRIPTORS: tuple[RuleDescriptor, ...] = (
|
|
11
|
+
RuleDescriptor(
|
|
12
|
+
rule_id=RuleId("prefer-validated-uuid-comparison"),
|
|
13
|
+
module="sarj_python_lint.rules.prefer_validated_uuid_comparison",
|
|
14
|
+
class_name="PreferValidatedUuidComparison",
|
|
15
|
+
code="SARJ562",
|
|
16
|
+
description="Validate UUID identifiers at input boundaries before comparing them.",
|
|
17
|
+
aliases=(),
|
|
18
|
+
enabled_by_default=True,
|
|
19
|
+
),
|
|
20
|
+
RuleDescriptor(
|
|
21
|
+
rule_id=RuleId("prefer-single-psycopg-cursor"),
|
|
22
|
+
module="sarj_python_lint.rules.prefer_single_psycopg_cursor",
|
|
23
|
+
class_name="PreferSinglePsycopgCursor",
|
|
24
|
+
code="SARJ561",
|
|
25
|
+
description="Review repeated Psycopg cursor creation within one function.",
|
|
26
|
+
aliases=(),
|
|
27
|
+
enabled_by_default=True,
|
|
28
|
+
),
|
|
29
|
+
RuleDescriptor(
|
|
30
|
+
rule_id=RuleId("no-psycopg-execution-in-loop"),
|
|
31
|
+
module="sarj_python_lint.rules.no_psycopg_execution_in_loop",
|
|
32
|
+
class_name="NoPsycopgExecutionInLoop",
|
|
33
|
+
code="SARJ560",
|
|
34
|
+
description="Use bounded bulk queries instead of executing Psycopg operations inside item loops.",
|
|
35
|
+
aliases=(),
|
|
36
|
+
enabled_by_default=True,
|
|
37
|
+
),
|
|
11
38
|
RuleDescriptor(
|
|
12
39
|
rule_id=RuleId("no-shared-mutable-default"),
|
|
13
40
|
module="sarj_python_lint.rules.no_shared_mutable_default",
|
|
@@ -112,7 +139,7 @@ DESCRIPTORS: tuple[RuleDescriptor, ...] = (
|
|
|
112
139
|
module="sarj_python_lint.rules.no_custom_control_flow_exception",
|
|
113
140
|
class_name="NoCustomControlFlowException",
|
|
114
141
|
code="SARJ542",
|
|
115
|
-
description="Return typed outcomes for expected domain failures instead of
|
|
142
|
+
description="Return typed outcomes for expected domain failures instead of marker or fixed-message exceptions.",
|
|
116
143
|
aliases=(),
|
|
117
144
|
enabled_by_default=True,
|
|
118
145
|
),
|
{sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_registry.py
RENAMED
|
@@ -248,6 +248,7 @@ from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossi
|
|
|
248
248
|
from sarj_python_lint.rules.no_invalid_pydantic_bypass import NoInvalidPydanticBypass
|
|
249
249
|
from sarj_python_lint.rules.no_invalid_pydantic_validator_return import NoInvalidPydanticValidatorReturn
|
|
250
250
|
from sarj_python_lint.rules.no_partial_type_is import NoPartialTypeIs
|
|
251
|
+
from sarj_python_lint.rules.no_psycopg_execution_in_loop import NoPsycopgExecutionInLoop
|
|
251
252
|
from sarj_python_lint.rules.no_recording_store import NoRecordingStore
|
|
252
253
|
from sarj_python_lint.rules.no_redundant_local_alias import NoRedundantLocalAlias
|
|
253
254
|
from sarj_python_lint.rules.no_redundant_sql_alias import NoRedundantSqlAlias
|
|
@@ -268,9 +269,11 @@ from sarj_python_lint.rules.prefer_psycopg_executemany import PreferPsycopgExecu
|
|
|
268
269
|
from sarj_python_lint.rules.prefer_pydantic_model_assertion import PreferPydanticModelAssertion
|
|
269
270
|
from sarj_python_lint.rules.prefer_pytest_dependency_fixtures import PreferPytestDependencyFixtures
|
|
270
271
|
from sarj_python_lint.rules.prefer_runtime_validation import PreferRuntimeValidation
|
|
272
|
+
from sarj_python_lint.rules.prefer_single_psycopg_cursor import PreferSinglePsycopgCursor
|
|
271
273
|
from sarj_python_lint.rules.prefer_typed_cli import PreferTypedCli
|
|
272
274
|
from sarj_python_lint.rules.prefer_typed_mapping_get import PreferTypedMappingGet
|
|
273
275
|
from sarj_python_lint.rules.prefer_typed_settings_field import PreferTypedSettingsField
|
|
276
|
+
from sarj_python_lint.rules.prefer_validated_uuid_comparison import PreferValidatedUuidComparison
|
|
274
277
|
from sarj_python_lint.rules.pytest_fixture_returns_closed_resource import PytestFixtureReturnsClosedResource
|
|
275
278
|
from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
|
|
276
279
|
from sarj_python_lint.rules.repeated_unimplemented_test_contract import RepeatedUnimplementedTestContract
|
|
@@ -286,6 +289,9 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
|
|
|
286
289
|
|
|
287
290
|
_AUTHORING_REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
288
291
|
{
|
|
292
|
+
PreferValidatedUuidComparison.id: PreferValidatedUuidComparison,
|
|
293
|
+
PreferSinglePsycopgCursor.id: PreferSinglePsycopgCursor,
|
|
294
|
+
NoPsycopgExecutionInLoop.id: NoPsycopgExecutionInLoop,
|
|
289
295
|
NoSharedMutableDefault.id: NoSharedMutableDefault,
|
|
290
296
|
NoSysPathMutation.id: NoSysPathMutation,
|
|
291
297
|
NoDynamicBuiltins.id: NoDynamicBuiltins,
|
{sarj_python_lint-1.109.0 → sarj_python_lint-1.110.0}/src/sarj_python_lint/rules/_store_sql.py
RENAMED
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
|
|
|
8
8
|
from sqlglot import exp
|
|
9
9
|
|
|
10
10
|
from sarj_python_lint.rules._sql import sql_string_value
|
|
11
|
-
from sarj_python_lint.rules._store_writes import Function, methods
|
|
11
|
+
from sarj_python_lint.rules._store_writes import Function, methods, own_nodes
|
|
12
12
|
from sarj_python_lint.rules.no_psycopg_execution_outside_injected_owner import psycopg_execution_calls
|
|
13
13
|
|
|
14
14
|
|
|
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
_HOLE = "__sarj_store_sql_hole__"
|
|
22
22
|
_FORMAT_HOLE = re.compile(r"(?<!\{)\{(?:[A-Za-z_]\w*|\d*)\}(?!\})")
|
|
23
23
|
_MAX_RECOVERY_DEPTH = 8
|
|
24
|
+
_MAX_APPEND_VARIANTS = 16
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass(frozen=True)
|
|
@@ -44,6 +45,25 @@ class _MethodWrites:
|
|
|
44
45
|
instances: frozenset[tuple[ast.ClassDef, str]]
|
|
45
46
|
|
|
46
47
|
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class _QueryVariants:
|
|
50
|
+
origin: ast.expr
|
|
51
|
+
texts: tuple[str, ...]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class _AppendDefinition:
|
|
56
|
+
assignment: ast.Assign
|
|
57
|
+
writes: list[ast.AST | None]
|
|
58
|
+
function: Function
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class _LiteralAppend:
|
|
63
|
+
text: str
|
|
64
|
+
conditional: bool
|
|
65
|
+
|
|
66
|
+
|
|
47
67
|
def store_queries(context: PythonFileContext) -> Iterator[StoreQuery]:
|
|
48
68
|
if context.store_queries is None:
|
|
49
69
|
try:
|
|
@@ -151,8 +171,17 @@ def query_owner(node: ast.AST, context: PythonFileContext) -> QueryOwner:
|
|
|
151
171
|
|
|
152
172
|
def _parsed_query(expression: ast.expr, call: ast.Call, context: PythonFileContext) -> Iterator[StoreQuery]:
|
|
153
173
|
text = _query_text(expression, context, seen=frozenset())
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
origin = _query_origin(expression, context)
|
|
175
|
+
texts = (text,) if text is not None else ()
|
|
176
|
+
if not texts and isinstance(expression, ast.Name):
|
|
177
|
+
appended = _literal_append_variants(expression, context)
|
|
178
|
+
if appended is not None:
|
|
179
|
+
origin, texts = appended.origin, appended.texts
|
|
180
|
+
for query_text in texts:
|
|
181
|
+
yield from _parse_query_text(query_text, origin, call, context)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _parse_query_text(text: str, origin: ast.expr, call: ast.Call, context: PythonFileContext) -> Iterator[StoreQuery]:
|
|
156
185
|
from sqlglot.errors import SqlglotError # ruff: ignore[import-outside-top-level] -- malformed input abstains.
|
|
157
186
|
|
|
158
187
|
from sarj_python_lint.rules._postgres import parse_postgres # ruff: ignore[import-outside-top-level] -- avoid private-source unsupported SQL logs.
|
|
@@ -164,7 +193,81 @@ def _parsed_query(expression: ast.expr, call: ast.Call, context: PythonFileConte
|
|
|
164
193
|
scope = query_owner(call, context)
|
|
165
194
|
for statement in statements:
|
|
166
195
|
if statement is not None:
|
|
167
|
-
yield StoreQuery(
|
|
196
|
+
yield StoreQuery(origin, scope.owner, scope.method, text, statement)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _literal_append_variants(expression: ast.Name, context: PythonFileContext) -> _QueryVariants | None:
|
|
200
|
+
definition = _append_definition(expression, context)
|
|
201
|
+
if definition is None:
|
|
202
|
+
return None
|
|
203
|
+
value = definition.assignment.value
|
|
204
|
+
if not isinstance(value, ast.Constant) or not isinstance(value.value, str):
|
|
205
|
+
return None
|
|
206
|
+
variants = (value.value,)
|
|
207
|
+
for write in sorted(definition.writes, key=lambda node: node.lineno if isinstance(node, ast.stmt) else 0):
|
|
208
|
+
if write is definition.assignment:
|
|
209
|
+
continue
|
|
210
|
+
append = _literal_append(write, definition, expression, context)
|
|
211
|
+
if append is None:
|
|
212
|
+
return None
|
|
213
|
+
appended = tuple(text + append.text for text in variants)
|
|
214
|
+
variants = (*variants, *appended) if append.conditional else appended
|
|
215
|
+
if len(variants) > _MAX_APPEND_VARIANTS:
|
|
216
|
+
return None
|
|
217
|
+
return _QueryVariants(value, tuple(dict.fromkeys(variants)))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _append_definition(expression: ast.Name, context: PythonFileContext) -> _AppendDefinition | None:
|
|
221
|
+
if context.static_resource_provenance.dynamic_namespace or context.static_resource_provenance.wildcard:
|
|
222
|
+
return None
|
|
223
|
+
function = query_owner(expression, context).method
|
|
224
|
+
if function is None:
|
|
225
|
+
return None
|
|
226
|
+
writes = [
|
|
227
|
+
node
|
|
228
|
+
for node in own_nodes(function)
|
|
229
|
+
if isinstance(node, ast.Name) and node.id == expression.id and isinstance(node.ctx, ast.Store | ast.Del)
|
|
230
|
+
]
|
|
231
|
+
assignments = [context.parents.get(node) for node in writes]
|
|
232
|
+
initial = [
|
|
233
|
+
node
|
|
234
|
+
for node in assignments
|
|
235
|
+
if isinstance(node, ast.Assign)
|
|
236
|
+
and len(node.targets) == 1
|
|
237
|
+
and node in function.body
|
|
238
|
+
and node.lineno < expression.lineno
|
|
239
|
+
]
|
|
240
|
+
if len(initial) != 1:
|
|
241
|
+
return None
|
|
242
|
+
if any(expression.id in node.names for node in context.nodes(ast.Global, ast.Nonlocal)):
|
|
243
|
+
return None
|
|
244
|
+
return _AppendDefinition(initial[0], assignments, function)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _literal_append(
|
|
248
|
+
write: ast.AST | None,
|
|
249
|
+
definition: _AppendDefinition,
|
|
250
|
+
expression: ast.Name,
|
|
251
|
+
context: PythonFileContext,
|
|
252
|
+
) -> _LiteralAppend | None:
|
|
253
|
+
if not (
|
|
254
|
+
isinstance(write, ast.AugAssign)
|
|
255
|
+
and isinstance(write.op, ast.Add)
|
|
256
|
+
and isinstance(write.value, ast.Constant)
|
|
257
|
+
and isinstance(write.value.value, str)
|
|
258
|
+
and definition.assignment.lineno < write.lineno < expression.lineno
|
|
259
|
+
):
|
|
260
|
+
return None
|
|
261
|
+
parent = context.parents.get(write)
|
|
262
|
+
conditional = (
|
|
263
|
+
isinstance(parent, ast.If)
|
|
264
|
+
and parent.body == [write]
|
|
265
|
+
and not parent.orelse
|
|
266
|
+
and parent in definition.function.body
|
|
267
|
+
)
|
|
268
|
+
if parent is not definition.function and not conditional:
|
|
269
|
+
return None
|
|
270
|
+
return _LiteralAppend(write.value.value, conditional)
|
|
168
271
|
|
|
169
272
|
|
|
170
273
|
def _query_origin(expression: ast.expr, context: PythonFileContext) -> ast.expr:
|
|
@@ -226,6 +329,8 @@ def _query_text(expression: ast.expr, context: PythonFileContext, *, seen: froze
|
|
|
226
329
|
return literal
|
|
227
330
|
match expression:
|
|
228
331
|
case ast.Name():
|
|
332
|
+
if context.static_resource_provenance.dynamic_namespace:
|
|
333
|
+
return None
|
|
229
334
|
value = _bound_value(expression, context)
|
|
230
335
|
return _query_text(value, context, seen=seen) if value is not None else None
|
|
231
336
|
case ast.Call(func=ast.Attribute(attr="format", value=value)):
|
|
@@ -58,9 +58,9 @@ class NoCustomControlFlowException(Rule):
|
|
|
58
58
|
code = "SARJ542"
|
|
59
59
|
documentation = RuleDocumentation(
|
|
60
60
|
default_level=Severity.WARNING,
|
|
61
|
-
summary="Return typed outcomes for expected domain failures instead of
|
|
61
|
+
summary="Return typed outcomes for expected domain failures instead of marker or fixed-message exceptions.",
|
|
62
62
|
rationale=(
|
|
63
|
-
"
|
|
63
|
+
"A marker or fixed-message custom exception creates another control-flow type without declaring failure data in "
|
|
64
64
|
"the function's return contract. Expected domain outcomes can often use tagged result variants "
|
|
65
65
|
"that callers handle explicitly, while exceptional failures and framework rejection still need exceptions."
|
|
66
66
|
),
|
|
@@ -73,7 +73,7 @@ class NoCustomControlFlowException(Rule):
|
|
|
73
73
|
category=RuleCategory.ARCHITECTURE,
|
|
74
74
|
autofix=AutofixPolicy.NONE,
|
|
75
75
|
limitations=(
|
|
76
|
-
"This review warning checks stable module-level
|
|
76
|
+
"This review warning checks stable module-level Exception/ValueError markers and fixed-message subclasses whose only behavior is a zero-argument initializer calling super().__init__ with one literal string. A source-visible direct raise is required. Fixed-message subclasses of a directly imported base also qualify for review; imported base semantics remain a manual contract check. Payload fields, dynamic messages, extra behavior, decorators, generic/ambiguous bases and unused definitions abstain.",
|
|
77
77
|
"Raising a ValueError-derived marker from an import-proven Pydantic field/model validator or legacy validator preserves that rejection protocol. Direct synchronous module callbacks used only as Annotated validator metadata on plain direct BaseModel fields, and stable nonescaping direct nested helpers, also preserve rejection; lexical helper ownership is bounded to 32 levels. Unknown external bases and visible mutations or escapes of the class or a local base to registrations, callbacks or other value consumers abstain; HTTPException and other framework-required exception types remain valid.",
|
|
78
78
|
"Expectedness is not inferred from a name, message or prefix. Genuine exceptional markers may also match and require review rather than conversion. Cross-module callers and registrations, indirect raises, dynamically mutated namespaces and class aliases are not inferred. Only simple stable module assignment aliases and nongeneric annotation aliases consumed directly by model fields are followed. Explicit string annotations, custom model hooks, async/generator callbacks, escaped helpers and broader call graphs are not followed. Generic lexical resolution can miss a module marker shadowed only in a method's class namespace.",
|
|
79
79
|
"Tests, generated and vendor code are excluded. No automatic rewrite is safe: a result return must preserve failure handling and transaction/resource unwinding. SARJ435 separately reviews joined-string reason identities.",
|
|
@@ -144,7 +144,7 @@ class NoCustomControlFlowException(Rule):
|
|
|
144
144
|
marker.lineno,
|
|
145
145
|
marker.col_offset + 1,
|
|
146
146
|
self.code,
|
|
147
|
-
f"Review `{marker.name}`: it adds only an exception identity and is directly raised. "
|
|
147
|
+
f"Review `{marker.name}`: it adds only an exception identity or fixed message and is directly raised. "
|
|
148
148
|
"If this is an expected domain outcome, return a typed tagged result; preserve genuine "
|
|
149
149
|
"exceptional failures, validation and framework contracts.",
|
|
150
150
|
severity=Severity.WARNING,
|
|
@@ -158,17 +158,15 @@ def _markers(context: PythonFileContext, provenance: ResourceProvenance) -> dict
|
|
|
158
158
|
return {}
|
|
159
159
|
markers: dict[str, ast.ClassDef] = {}
|
|
160
160
|
for node in context.tree.body:
|
|
161
|
-
if not isinstance(node, ast.ClassDef) or not
|
|
161
|
+
if not isinstance(node, ast.ClassDef) or not _marker_body(node):
|
|
162
162
|
continue
|
|
163
163
|
if provenance.binding(node.name, node) is not node:
|
|
164
164
|
continue
|
|
165
165
|
base = node.bases[0]
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
if not (local or _builtin_base(base, context, provenance)):
|
|
166
|
+
fixed_message = any(isinstance(member, ast.FunctionDef) for member in node.body)
|
|
167
|
+
if not _marker_base(base, markers, context, fixed_message=fixed_message):
|
|
168
|
+
continue
|
|
169
|
+
if fixed_message and not context.imports.builtin_is_unshadowed("super"):
|
|
172
170
|
continue
|
|
173
171
|
if node.name in provenance.mutated or node.name in provenance.external_bindings:
|
|
174
172
|
continue
|
|
@@ -178,7 +176,22 @@ def _markers(context: PythonFileContext, provenance: ResourceProvenance) -> dict
|
|
|
178
176
|
return markers
|
|
179
177
|
|
|
180
178
|
|
|
181
|
-
def
|
|
179
|
+
def _marker_base(
|
|
180
|
+
base: ast.expr, markers: dict[str, ast.ClassDef], context: PythonFileContext, *, fixed_message: bool
|
|
181
|
+
) -> bool:
|
|
182
|
+
provenance = context.static_resource_provenance
|
|
183
|
+
local = isinstance(base, ast.Name) and base.id in markers and provenance.binding(base.id, base) is markers[base.id]
|
|
184
|
+
if local or _builtin_base(base, context, provenance):
|
|
185
|
+
return True
|
|
186
|
+
imported = provenance.runtime_import_name(base)
|
|
187
|
+
return (
|
|
188
|
+
fixed_message
|
|
189
|
+
and imported is not None
|
|
190
|
+
and not imported.startswith(("builtins.", "asyncio.", "fastapi.", "starlette.", "concurrent.futures."))
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _marker_body(node: ast.ClassDef) -> bool:
|
|
182
195
|
return (
|
|
183
196
|
len(node.bases) == 1
|
|
184
197
|
and not node.decorator_list
|
|
@@ -186,6 +199,7 @@ def _name_only(node: ast.ClassDef) -> bool:
|
|
|
186
199
|
and not node.type_params
|
|
187
200
|
and all(
|
|
188
201
|
isinstance(member, ast.Pass)
|
|
202
|
+
or (isinstance(member, ast.FunctionDef) and _fixed_message_initializer(member))
|
|
189
203
|
or (
|
|
190
204
|
isinstance(member, ast.Expr)
|
|
191
205
|
and isinstance(member.value, ast.Constant)
|
|
@@ -196,6 +210,29 @@ def _name_only(node: ast.ClassDef) -> bool:
|
|
|
196
210
|
)
|
|
197
211
|
|
|
198
212
|
|
|
213
|
+
def _fixed_message_initializer(node: ast.FunctionDef) -> bool:
|
|
214
|
+
arguments = [*node.args.posonlyargs, *node.args.args]
|
|
215
|
+
if node.name != "__init__" or node.decorator_list or node.type_params or len(arguments) != 1:
|
|
216
|
+
return False
|
|
217
|
+
if node.args.vararg or node.args.kwarg or node.args.kwonlyargs or node.args.defaults:
|
|
218
|
+
return False
|
|
219
|
+
match node.body:
|
|
220
|
+
case [
|
|
221
|
+
ast.Expr(
|
|
222
|
+
value=ast.Call(
|
|
223
|
+
func=ast.Attribute(
|
|
224
|
+
value=ast.Call(func=ast.Name(id="super"), args=[], keywords=[]), attr="__init__"
|
|
225
|
+
),
|
|
226
|
+
args=[ast.Constant(value=str())],
|
|
227
|
+
keywords=[],
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
]:
|
|
231
|
+
return True
|
|
232
|
+
case _:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|
|
199
236
|
def _builtin_namespace_safe(context: PythonFileContext) -> bool:
|
|
200
237
|
# Direct exception imports expose immutable class objects.
|
|
201
238
|
for node in context.nodes(ast.Import, ast.ImportFrom, ast.Name):
|
|
@@ -250,7 +287,7 @@ def _value_error_marker(marker: ast.ClassDef, context: PythonFileContext, proven
|
|
|
250
287
|
) or provenance.imported(base, "builtins.ValueError"):
|
|
251
288
|
return True
|
|
252
289
|
bound = provenance.binding(base.id, base) if isinstance(base, ast.Name) else None
|
|
253
|
-
if not isinstance(bound, ast.ClassDef) or not
|
|
290
|
+
if not isinstance(bound, ast.ClassDef) or not _marker_body(bound):
|
|
254
291
|
return False
|
|
255
292
|
current = bound
|
|
256
293
|
return False
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING, final, override
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import (
|
|
8
|
+
AutofixPolicy,
|
|
9
|
+
Diagnostic,
|
|
10
|
+
ExampleFile,
|
|
11
|
+
ExampleOutcome,
|
|
12
|
+
Rule,
|
|
13
|
+
RuleCategory,
|
|
14
|
+
RuleDocumentation,
|
|
15
|
+
RuleExample,
|
|
16
|
+
Severity,
|
|
17
|
+
is_suppressed,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._store_writes import known_single_iteration
|
|
20
|
+
from sarj_python_lint.rules.no_psycopg_execution_outside_injected_owner import psycopg_execution_calls
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_BAD = "from psycopg import AsyncConnection\nasync def remove(connection: AsyncConnection, rows):\n for row in rows:\n await connection.execute('DELETE FROM item WHERE id = %s', (row.id,))\n"
|
|
28
|
+
_GOOD = "from psycopg import AsyncConnection\nasync def remove(connection: AsyncConnection, rows):\n identifiers = [row.id for row in rows]\n await connection.execute('DELETE FROM item WHERE id = ANY(%s)', (identifiers,))\n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@final
|
|
32
|
+
class NoPsycopgExecutionInLoop(Rule):
|
|
33
|
+
id = "no-psycopg-execution-in-loop"
|
|
34
|
+
code = "SARJ560"
|
|
35
|
+
documentation = RuleDocumentation(
|
|
36
|
+
default_level=Severity.WARNING,
|
|
37
|
+
summary="Use bounded bulk queries instead of executing Psycopg operations inside item loops.",
|
|
38
|
+
rationale="Executing a database operation for each input item makes round trips scale with collection size, including loops that prepare each row before executing.",
|
|
39
|
+
remediation="Collect inputs and use a bounded set-based query, typed bulk store operation or executemany batch. Preserve tenant checks, revision predicates, transaction boundaries, returned rows, ordering and deliberate sequential effects.",
|
|
40
|
+
category=RuleCategory.PERFORMANCE,
|
|
41
|
+
autofix=AutofixPolicy.NONE,
|
|
42
|
+
limitations=(
|
|
43
|
+
"Checks import-proven Psycopg execute calls in for/async-for bodies and repeated comprehension expressions, including injected pools, connections and cursors. Explicit executemany batch loops remain valid for bounded chunking.",
|
|
44
|
+
"In-memory preparation, one-shot iteration inputs, known zero/single-item loops, deferred nested functions, unrelated execute methods and generated code are excluded. While retry/stream loops and interprocedural helper execution are not inferred.",
|
|
45
|
+
"SARJ518 owns its narrower invariant-write cases, so one location receives only that specific executemany diagnostic. Unknown SQL and result-consuming loops still require bulk-query review; no automatic rewrite changes database semantics.",
|
|
46
|
+
),
|
|
47
|
+
examples=(
|
|
48
|
+
RuleExample(
|
|
49
|
+
example_id="per-item-write",
|
|
50
|
+
title="A connection executes once per item",
|
|
51
|
+
outcome=ExampleOutcome.MATCH,
|
|
52
|
+
files=(ExampleFile.python("app/store.py", _BAD),),
|
|
53
|
+
focus_path=PurePosixPath("app/store.py"),
|
|
54
|
+
expected_count=1,
|
|
55
|
+
public=True,
|
|
56
|
+
),
|
|
57
|
+
RuleExample(
|
|
58
|
+
example_id="set-based-write",
|
|
59
|
+
title="One query consumes the collected identifiers",
|
|
60
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
61
|
+
files=(ExampleFile.python("app/store.py", _GOOD),),
|
|
62
|
+
focus_path=PurePosixPath("app/store.py"),
|
|
63
|
+
expected_count=0,
|
|
64
|
+
public=True,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
description = documentation.summary
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
72
|
+
if context.generated or context.tree is None:
|
|
73
|
+
return []
|
|
74
|
+
calls = [
|
|
75
|
+
call
|
|
76
|
+
for call in psycopg_execution_calls(context, include_injected=True, include_probes=False)
|
|
77
|
+
if isinstance(call.func, ast.Attribute) and call.func.attr == "execute" and _repeated(call, context)
|
|
78
|
+
]
|
|
79
|
+
if not calls:
|
|
80
|
+
return []
|
|
81
|
+
unique = {(call.lineno, call.col_offset): call for call in calls}
|
|
82
|
+
return [
|
|
83
|
+
Diagnostic(
|
|
84
|
+
context.path,
|
|
85
|
+
call.lineno,
|
|
86
|
+
call.col_offset + 1,
|
|
87
|
+
self.code,
|
|
88
|
+
"This item loop executes a Psycopg operation per iteration; collect inputs and prefer a bounded bulk query or bulk store method, preserving transaction, result and failure semantics.",
|
|
89
|
+
severity=Severity.WARNING,
|
|
90
|
+
)
|
|
91
|
+
for call in unique.values()
|
|
92
|
+
if not is_suppressed(context.source_lines, call.lineno, self.code)
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _repeated(call: ast.Call, context: PythonFileContext) -> bool:
|
|
97
|
+
child: ast.AST = call
|
|
98
|
+
parent = context.parents.get(child)
|
|
99
|
+
while parent is not None:
|
|
100
|
+
if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda | ast.ClassDef):
|
|
101
|
+
return False
|
|
102
|
+
if (
|
|
103
|
+
isinstance(parent, ast.For | ast.AsyncFor)
|
|
104
|
+
and child in parent.body
|
|
105
|
+
and not known_single_iteration(parent.iter)
|
|
106
|
+
):
|
|
107
|
+
return True
|
|
108
|
+
if isinstance(parent, ast.comprehension):
|
|
109
|
+
owner = context.parents.get(parent)
|
|
110
|
+
if child in parent.ifs or (
|
|
111
|
+
child is parent.iter
|
|
112
|
+
and isinstance(owner, ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp)
|
|
113
|
+
and owner.generators.index(parent) > 0
|
|
114
|
+
):
|
|
115
|
+
return True
|
|
116
|
+
if isinstance(parent, ast.ListComp | ast.SetComp | ast.GeneratorExp) and child is parent.elt:
|
|
117
|
+
return True
|
|
118
|
+
if isinstance(parent, ast.DictComp) and child in {parent.key, parent.value}:
|
|
119
|
+
return True
|
|
120
|
+
child, parent = parent, context.parents.get(parent)
|
|
121
|
+
return False
|
|
@@ -156,12 +156,26 @@ class NoPsycopgExecutionOutsideInjectedOwner(Rule):
|
|
|
156
156
|
|
|
157
157
|
def psycopg_execution_calls(
|
|
158
158
|
context: PythonFileContext, *, include_injected: bool, include_probes: bool
|
|
159
|
+
) -> list[ast.Call]:
|
|
160
|
+
return _psycopg_operation_calls(
|
|
161
|
+
context, include_injected=include_injected, include_probes=include_probes, operations=_EXECUTE_METHODS
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def psycopg_cursor_calls(context: PythonFileContext) -> list[ast.Call]:
|
|
166
|
+
return _psycopg_operation_calls(
|
|
167
|
+
context, include_injected=True, include_probes=True, operations=frozenset({"cursor"})
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _psycopg_operation_calls(
|
|
172
|
+
context: PythonFileContext, *, include_injected: bool, include_probes: bool, operations: frozenset[str]
|
|
159
173
|
) -> list[ast.Call]:
|
|
160
174
|
tree = context.tree
|
|
161
175
|
if tree is None:
|
|
162
176
|
return []
|
|
163
177
|
if not any(
|
|
164
|
-
isinstance(call.func, ast.Attribute) and call.func.attr in
|
|
178
|
+
isinstance(call.func, ast.Attribute) and call.func.attr in operations for call in context.nodes(ast.Call)
|
|
165
179
|
):
|
|
166
180
|
return []
|
|
167
181
|
mutated_modules: frozenset[str] | None = frozenset()
|
|
@@ -184,6 +198,7 @@ def psycopg_execution_calls(
|
|
|
184
198
|
include_probes=include_probes,
|
|
185
199
|
track_cursor_parameters=include_injected or scope.owner is None,
|
|
186
200
|
constructor=scope.owner is not None and scope.function.name == "__init__",
|
|
201
|
+
operations=operations,
|
|
187
202
|
).analyze(scope.function)
|
|
188
203
|
)
|
|
189
204
|
return calls
|
|
@@ -495,6 +510,7 @@ class _FunctionAnalyzer:
|
|
|
495
510
|
include_probes: bool,
|
|
496
511
|
track_cursor_parameters: bool,
|
|
497
512
|
constructor: bool,
|
|
513
|
+
operations: frozenset[str],
|
|
498
514
|
) -> None:
|
|
499
515
|
self._types = types
|
|
500
516
|
self._annotation_types = annotation_types
|
|
@@ -503,6 +519,7 @@ class _FunctionAnalyzer:
|
|
|
503
519
|
self._include_probes = include_probes
|
|
504
520
|
self._track_cursor_parameters = track_cursor_parameters
|
|
505
521
|
self._constructor = constructor
|
|
522
|
+
self._operations = operations
|
|
506
523
|
self._findings: list[ast.Call] = []
|
|
507
524
|
self._shadowed: set[str] = set()
|
|
508
525
|
|
|
@@ -673,7 +690,7 @@ class _FunctionAnalyzer:
|
|
|
673
690
|
def visit_Call(self, node: ast.Call) -> None:
|
|
674
691
|
if (
|
|
675
692
|
isinstance(node.func, ast.Attribute)
|
|
676
|
-
and node.func.attr in
|
|
693
|
+
and node.func.attr in analyzer._operations
|
|
677
694
|
and analyzer._expression_origin(node.func.value, state) in analyzer._allowed_origins
|
|
678
695
|
and (analyzer._include_probes or not _is_connection_probe(node))
|
|
679
696
|
):
|