sarj-python-lint 1.99.0__tar.gz → 1.101.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.99.0 → sarj_python_lint-1.101.0}/PKG-INFO +2 -2
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/pyproject.toml +2 -2
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix.py +41 -9
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_parallel.py +12 -7
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_copy_on_write.py +6 -8
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_namespace_access.py +1 -1
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_project_index.py +19 -3
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_resource_provenance.py +2 -1
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_sql.py +9 -2
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_store_sql.py +5 -1
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +71 -38
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +4 -3
- sarj_python_lint-1.101.0/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +476 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_delete_statement.py +2 -2
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +3 -2
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +122 -19
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +1 -1
- sarj_python_lint-1.99.0/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -251
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/.gitignore +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/LICENSE +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/README.md +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_rule_descriptors.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.99.0 → sarj_python_lint-1.101.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.101.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/
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: packaging==26.3
|
|
21
21
|
Requires-Dist: ruff>=0.17.2
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.6.0
|
|
23
23
|
Requires-Dist: sqlglot==30.24.0
|
|
24
24
|
Requires-Dist: typer==0.27.3
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.101.0"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["sarj-rule-contracts==0.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.6.0", "packaging==26.3", "ruff>=0.17.2", "sqlglot==30.24.0", "typer==0.27.3"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -2,13 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from bisect import bisect_left, insort
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from functools import cached_property
|
|
6
7
|
from hashlib import sha256
|
|
7
8
|
from io import StringIO
|
|
8
9
|
from itertools import pairwise
|
|
9
10
|
import re
|
|
10
11
|
import tokenize
|
|
11
|
-
from typing import TYPE_CHECKING, Final, NamedTuple
|
|
12
|
+
from typing import TYPE_CHECKING, Final, Literal, NamedTuple
|
|
12
13
|
from unicodedata import normalize
|
|
13
14
|
|
|
14
15
|
from sarj_rule_contracts import TextEdit, apply_edits
|
|
@@ -80,6 +81,8 @@ _WALRUS_CODES = frozenset({"SARJ081", "SARJ432"})
|
|
|
80
81
|
_JWT_POSITIONAL_PREFIX_ARGUMENTS = 3
|
|
81
82
|
_STREAM_MIN_BODY_LENGTH = 3
|
|
82
83
|
_CONSTANT_TRUE_DEPTH_LIMIT = 32
|
|
84
|
+
# Bound accepted revisions; each rule still owns its work and replacement size.
|
|
85
|
+
_MAX_FIX_PASSES: Final = 256
|
|
83
86
|
_PURE_STRING_TRANSFORMS: Final[dict[str, str]] = {
|
|
84
87
|
"strip": "stripped_value",
|
|
85
88
|
"lower": "lowered_value",
|
|
@@ -1360,6 +1363,18 @@ _BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
|
|
|
1360
1363
|
}
|
|
1361
1364
|
|
|
1362
1365
|
|
|
1366
|
+
@dataclass(frozen=True, slots=True)
|
|
1367
|
+
class _FixCandidate:
|
|
1368
|
+
code: str
|
|
1369
|
+
edits: tuple[TextEdit, ...]
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
@dataclass(frozen=True, slots=True)
|
|
1373
|
+
class _FixPass:
|
|
1374
|
+
edits: tuple[TextEdit, ...]
|
|
1375
|
+
codes: tuple[str, ...]
|
|
1376
|
+
|
|
1377
|
+
|
|
1363
1378
|
def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None) -> str:
|
|
1364
1379
|
from sarj_python_lint.__main__ import check_source # ruff: ignore[import-outside-top-level] -- the CLI imports this entry point lazily.
|
|
1365
1380
|
|
|
@@ -1371,6 +1386,8 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
|
|
|
1371
1386
|
# Only the closed alias builder owns aligned physical-line buffers and token coordinates here.
|
|
1372
1387
|
rules = [rule for rule in rules if rule.code == "SARJ531"]
|
|
1373
1388
|
seen: set[bytes] = set()
|
|
1389
|
+
applied_codes: set[str] = set()
|
|
1390
|
+
passes = 0
|
|
1374
1391
|
current = source
|
|
1375
1392
|
while (fingerprint := sha256(current.encode("utf-8")).digest()) not in seen:
|
|
1376
1393
|
seen.add(fingerprint)
|
|
@@ -1380,30 +1397,43 @@ def fix_source(path: Path, source: str, *, rule_ids: Sequence[str] | None = None
|
|
|
1380
1397
|
if not candidates:
|
|
1381
1398
|
return current
|
|
1382
1399
|
# Batch independent candidates; overlapping candidates get fresh offsets on the next pass.
|
|
1383
|
-
|
|
1400
|
+
selected_pass = _coherent_edits(candidates)
|
|
1401
|
+
updated = apply_edits(current, selected_pass.edits)
|
|
1384
1402
|
compile(updated, str(path), "exec", dont_inherit=True)
|
|
1385
1403
|
if updated == current:
|
|
1386
1404
|
return current
|
|
1405
|
+
if passes == _MAX_FIX_PASSES:
|
|
1406
|
+
raise _convergence_error(path, reason="pass budget exhausted", passes=passes, codes=applied_codes)
|
|
1407
|
+
passes += 1
|
|
1408
|
+
applied_codes.update(selected_pass.codes)
|
|
1387
1409
|
current = updated
|
|
1388
|
-
|
|
1389
|
-
|
|
1410
|
+
raise _convergence_error(path, reason="source cycle", passes=passes, codes=applied_codes)
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
def _convergence_error(
|
|
1414
|
+
path: Path, *, reason: Literal["pass budget exhausted", "source cycle"], passes: int, codes: set[str]
|
|
1415
|
+
) -> ValueError:
|
|
1416
|
+
participants = ", ".join(sorted(codes)) or "none"
|
|
1417
|
+
msg = f"Python autofixes did not converge: {path} ({reason}; passes={passes}; rules={participants})"
|
|
1418
|
+
return ValueError(msg)
|
|
1390
1419
|
|
|
1391
1420
|
|
|
1392
|
-
def _candidate_edits(context: PythonFileContext, diagnostics: Sequence[Diagnostic]) -> list[
|
|
1421
|
+
def _candidate_edits(context: PythonFileContext, diagnostics: Sequence[Diagnostic]) -> list[_FixCandidate]:
|
|
1393
1422
|
view = _Source(context, diagnostics)
|
|
1394
1423
|
return [
|
|
1395
|
-
edits
|
|
1424
|
+
_FixCandidate(diagnostic.code, tuple(edits))
|
|
1396
1425
|
for diagnostic in diagnostics
|
|
1397
1426
|
if (builder := _BUILDERS.get(diagnostic.code)) is not None
|
|
1398
1427
|
if (edits := builder(view, diagnostic))
|
|
1399
1428
|
]
|
|
1400
1429
|
|
|
1401
1430
|
|
|
1402
|
-
def _coherent_edits(candidates: list[
|
|
1431
|
+
def _coherent_edits(candidates: list[_FixCandidate]) -> _FixPass:
|
|
1403
1432
|
accepted: list[TextEdit] = []
|
|
1433
|
+
codes: list[str] = []
|
|
1404
1434
|
known: set[TextEdit] = set()
|
|
1405
1435
|
for candidate in candidates:
|
|
1406
|
-
additions = sorted(set(candidate) - known)
|
|
1436
|
+
additions = sorted(set(candidate.edits) - known)
|
|
1407
1437
|
if any(map(_overlap, additions, additions[1:], strict=False)):
|
|
1408
1438
|
continue
|
|
1409
1439
|
for edit in additions:
|
|
@@ -1416,7 +1446,9 @@ def _coherent_edits(candidates: list[list[TextEdit]]) -> list[TextEdit]:
|
|
|
1416
1446
|
for edit in additions:
|
|
1417
1447
|
insort(accepted, edit)
|
|
1418
1448
|
known.update(additions)
|
|
1419
|
-
|
|
1449
|
+
if additions:
|
|
1450
|
+
codes.append(candidate.code)
|
|
1451
|
+
return _FixPass(tuple(accepted), tuple(dict.fromkeys(codes)))
|
|
1420
1452
|
|
|
1421
1453
|
|
|
1422
1454
|
def _overlap(first: TextEdit, second: TextEdit) -> bool:
|
|
@@ -143,19 +143,24 @@ def check_parallel(
|
|
|
143
143
|
for path, source in loaded.items()
|
|
144
144
|
)
|
|
145
145
|
for result in pool.map(_check_file, items, chunksize=1, buffersize=workers * 4):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
needs_serial = True
|
|
146
|
+
# Drain actual results after either replay signal so a later worker
|
|
147
|
+
# failure cannot be disguised as a clean serial replay.
|
|
148
|
+
if needs_serial:
|
|
150
149
|
continue
|
|
151
150
|
resolutions.update(result.resolutions)
|
|
152
151
|
modules.update(result.modules)
|
|
153
152
|
# A worker cannot hit either order-dependent limit when the whole
|
|
154
153
|
# union stays strictly below it. At the boundary, discard all output.
|
|
155
|
-
if
|
|
156
|
-
|
|
154
|
+
if (
|
|
155
|
+
result.needs_serial
|
|
156
|
+
or len(resolutions) >= LOCAL_SOURCE_CACHE_LIMIT
|
|
157
|
+
or len(modules) >= LOCAL_SOURCE_CACHE_LIMIT
|
|
157
158
|
):
|
|
158
|
-
|
|
159
|
+
needs_serial = True
|
|
160
|
+
diagnostics.clear()
|
|
161
|
+
resolutions.clear()
|
|
162
|
+
modules.clear()
|
|
163
|
+
continue
|
|
159
164
|
diagnostics.extend(result.diagnostics)
|
|
160
165
|
return None if needs_serial else diagnostics
|
|
161
166
|
finally:
|
{sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
@@ -973,10 +973,8 @@ class CopyOnWriteAnalysis:
|
|
|
973
973
|
summaries[key] = _summary_value(_OwnershipSummary(alternatives))
|
|
974
974
|
bindings[name] = summaries[key]
|
|
975
975
|
|
|
976
|
-
def _scan(self, statements: list[ast.stmt], bindings: dict[str, _Value]) ->
|
|
977
|
-
for statement in statements
|
|
978
|
-
if self._statement(statement, bindings):
|
|
979
|
-
return
|
|
976
|
+
def _scan(self, statements: list[ast.stmt], bindings: dict[str, _Value]) -> bool:
|
|
977
|
+
return any(self._statement(statement, bindings) for statement in statements)
|
|
980
978
|
|
|
981
979
|
def _statement(self, statement: ast.stmt, bindings: dict[str, _Value]) -> bool:
|
|
982
980
|
match statement:
|
|
@@ -991,7 +989,7 @@ class CopyOnWriteAnalysis:
|
|
|
991
989
|
self._write(target, bindings)
|
|
992
990
|
self._bind(target, None, bindings)
|
|
993
991
|
case ast.If():
|
|
994
|
-
self._branches(statement, bindings)
|
|
992
|
+
return self._branches(statement, bindings)
|
|
995
993
|
case ast.For() | ast.AsyncFor():
|
|
996
994
|
self._for(statement, bindings)
|
|
997
995
|
case ast.Expr(value=ast.Call() as call):
|
|
@@ -1026,16 +1024,16 @@ class CopyOnWriteAnalysis:
|
|
|
1026
1024
|
self._write(target, bindings)
|
|
1027
1025
|
self._bind(target, value, bindings)
|
|
1028
1026
|
|
|
1029
|
-
def _branches(self, statement: ast.If, bindings: dict[str, _Value]) ->
|
|
1027
|
+
def _branches(self, statement: ast.If, bindings: dict[str, _Value]) -> bool:
|
|
1030
1028
|
self._expression(statement.test, bindings)
|
|
1031
1029
|
branches: list[dict[str, _Value]] = []
|
|
1032
1030
|
for positive, block in ((True, statement.body), (False, statement.orelse)):
|
|
1033
1031
|
branch = dict(bindings)
|
|
1034
1032
|
self._refine(statement.test, branch, positive=positive)
|
|
1035
|
-
self._scan(block, branch)
|
|
1036
|
-
if not block or not isinstance(block[-1], (ast.Return, ast.Raise)):
|
|
1033
|
+
if not self._scan(block, branch):
|
|
1037
1034
|
branches.append(branch)
|
|
1038
1035
|
self._join(branches, bindings)
|
|
1036
|
+
return not branches
|
|
1039
1037
|
|
|
1040
1038
|
def _for(self, statement: ast.For | ast.AsyncFor, bindings: dict[str, _Value]) -> None:
|
|
1041
1039
|
self._expression(statement.iter, bindings)
|
{sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
@@ -48,7 +48,7 @@ def dynamic_namespace_access(
|
|
|
48
48
|
def _namespace_api_aliases(nodes: Iterable[ast.AST]) -> set[str]:
|
|
49
49
|
aliases = set(_NAMESPACE_APIS) | {"__builtins__"}
|
|
50
50
|
for node in nodes:
|
|
51
|
-
if not isinstance(node, ast.ImportFrom) or node.module not in _MODULE_APIS:
|
|
51
|
+
if not isinstance(node, ast.ImportFrom) or node.level != 0 or node.module not in _MODULE_APIS:
|
|
52
52
|
continue
|
|
53
53
|
members = _MODULE_APIS[node.module]
|
|
54
54
|
aliases.update(alias.asname or alias.name for alias in node.names if alias.name in members)
|
{sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
@@ -156,6 +156,11 @@ class SourceUnit:
|
|
|
156
156
|
captured: CapturedSource | None = None
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
@dataclass(frozen=True, slots=True)
|
|
160
|
+
class _AmbiguousModuleSources:
|
|
161
|
+
paths: tuple[Path, ...]
|
|
162
|
+
|
|
163
|
+
|
|
159
164
|
@dataclass(frozen=True, slots=True)
|
|
160
165
|
class LoadedSource:
|
|
161
166
|
path: Path
|
|
@@ -276,8 +281,18 @@ class ProjectIndexSet:
|
|
|
276
281
|
self._bound_value_prefixes: dict[str, bool] = {}
|
|
277
282
|
self._value_origins_incomplete = False
|
|
278
283
|
self.import_namespace_mutations = ImportNamespaceMutations(WeakMethod(self._namespace_write_inventory))
|
|
279
|
-
by_module
|
|
280
|
-
|
|
284
|
+
by_module: dict[str, dict[Path, SourceUnit]] = {}
|
|
285
|
+
for unit in units.values():
|
|
286
|
+
if unit.module is not None:
|
|
287
|
+
by_module.setdefault(unit.module, {})[unit.path] = unit
|
|
288
|
+
self._by_module = MappingProxyType(
|
|
289
|
+
{
|
|
290
|
+
module: next(iter(candidates.values()))
|
|
291
|
+
if len(candidates) == 1
|
|
292
|
+
else _AmbiguousModuleSources(tuple(candidates))
|
|
293
|
+
for module, candidates in by_module.items()
|
|
294
|
+
}
|
|
295
|
+
)
|
|
281
296
|
classes: dict[SymbolRef, ClassSummary] = {}
|
|
282
297
|
nominals: dict[str, set[SymbolRef]] = {}
|
|
283
298
|
_collect_unit_symbols(units, classes, nominals)
|
|
@@ -429,7 +444,8 @@ class ProjectIndexSet:
|
|
|
429
444
|
return False
|
|
430
445
|
|
|
431
446
|
def source_unit(self, module: str) -> SourceUnit | None:
|
|
432
|
-
|
|
447
|
+
source = self._by_module.get(module)
|
|
448
|
+
return source if isinstance(source, SourceUnit) else None
|
|
433
449
|
|
|
434
450
|
def _namespace_write_inventory(self) -> Mapping[str, _ImportNamespaceWrites]:
|
|
435
451
|
def resolve(origin: SymbolRef) -> tuple[SymbolRef, ...] | None:
|
|
@@ -308,7 +308,8 @@ class ResourceProvenance:
|
|
|
308
308
|
or (
|
|
309
309
|
isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef)
|
|
310
310
|
and node in parent.decorator_list
|
|
311
|
-
and self.context.imports.resolved_qualified_name(node)
|
|
311
|
+
and self.context.imports.resolved_qualified_name(node)
|
|
312
|
+
in {"typing.final", "typing_extensions.final", "dataclasses.dataclass"}
|
|
312
313
|
)
|
|
313
314
|
)
|
|
314
315
|
|
|
@@ -27,11 +27,18 @@ def is_store_module(path: Path) -> bool:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def sql_string_value(node: ast.expr, *, interpolation_placeholder: str = " ") -> str | None:
|
|
30
|
+
try:
|
|
31
|
+
return _sql_string_value(node, interpolation_placeholder=interpolation_placeholder)
|
|
32
|
+
except RecursionError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _sql_string_value(node: ast.expr, *, interpolation_placeholder: str) -> str | None:
|
|
30
37
|
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
31
38
|
return node.value
|
|
32
39
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
33
|
-
left =
|
|
34
|
-
right =
|
|
40
|
+
left = _sql_string_value(node.left, interpolation_placeholder=interpolation_placeholder)
|
|
41
|
+
right = _sql_string_value(node.right, interpolation_placeholder=interpolation_placeholder)
|
|
35
42
|
if left is not None and right is not None:
|
|
36
43
|
return left + right
|
|
37
44
|
if isinstance(node, ast.JoinedStr):
|
{sarj_python_lint-1.99.0 → sarj_python_lint-1.101.0}/src/sarj_python_lint/rules/_store_sql.py
RENAMED
|
@@ -46,7 +46,11 @@ class _MethodWrites:
|
|
|
46
46
|
|
|
47
47
|
def store_queries(context: PythonFileContext) -> Iterator[StoreQuery]:
|
|
48
48
|
if context.store_queries is None:
|
|
49
|
-
|
|
49
|
+
try:
|
|
50
|
+
queries = tuple(recover_store_queries(context))
|
|
51
|
+
except RecursionError:
|
|
52
|
+
queries = ()
|
|
53
|
+
context.store_queries = queries
|
|
50
54
|
return iter(context.store_queries)
|
|
51
55
|
|
|
52
56
|
|
|
@@ -85,7 +85,11 @@ def _is_query_context(node: ast.expr, parents: Mapping[ast.AST, ast.AST], roots:
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def _query_roots(
|
|
88
|
-
tree: ast.Module,
|
|
88
|
+
tree: ast.Module,
|
|
89
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
90
|
+
*,
|
|
91
|
+
context: PythonFileContext,
|
|
92
|
+
node_index: NodeIndex | None = None,
|
|
89
93
|
) -> frozenset[int]:
|
|
90
94
|
binding_counts: dict[tuple[int, str], int] = {}
|
|
91
95
|
binding_values: dict[tuple[int, str], ast.expr] = {}
|
|
@@ -99,11 +103,12 @@ def _query_roots(
|
|
|
99
103
|
|
|
100
104
|
constructor_imports = _postgres_sql_constructors(tree, parents, node_index=node_index)
|
|
101
105
|
_query_binding_values(tree, parents, binding_values, node_index=node_index)
|
|
106
|
+
has_local_classes = bool(nodes(tree, ast.ClassDef, index=node_index))
|
|
102
107
|
|
|
103
108
|
roots: set[int] = set()
|
|
104
109
|
for call in nodes(tree, ast.Call, index=node_index):
|
|
105
110
|
argument = _query_argument(call)
|
|
106
|
-
if argument is None:
|
|
111
|
+
if argument is None or (has_local_classes and _source_local_receiver(call, context)):
|
|
107
112
|
continue
|
|
108
113
|
owner = _scope(call, parents)
|
|
109
114
|
constructors = _visible_constructors(owner, constructor_imports, parents, binding_counts, wildcard_scopes)
|
|
@@ -151,6 +156,21 @@ def _query_argument(call: ast.Call) -> ast.expr | None:
|
|
|
151
156
|
return next((keyword.value for keyword in call.keywords if keyword.arg in _QUERY_KEYWORDS), None)
|
|
152
157
|
|
|
153
158
|
|
|
159
|
+
def _source_local_receiver(call: ast.Call, context: PythonFileContext) -> bool:
|
|
160
|
+
if not isinstance(call.func, ast.Attribute) or not isinstance(call.func.value, (ast.Name, ast.Call)):
|
|
161
|
+
return False
|
|
162
|
+
provenance = context.static_resource_provenance
|
|
163
|
+
receiver: ast.expr | None = call.func.value
|
|
164
|
+
if isinstance(receiver, ast.Name):
|
|
165
|
+
binding = provenance.runtime_binding(receiver.id, call)
|
|
166
|
+
receiver = provenance.assignment_value(binding, call) if isinstance(binding, ast.Name) else None
|
|
167
|
+
return (
|
|
168
|
+
isinstance(receiver, ast.Call)
|
|
169
|
+
and isinstance(receiver.func, ast.Name)
|
|
170
|
+
and isinstance(provenance.runtime_binding(receiver.func.id, receiver), ast.ClassDef)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
154
174
|
def _postgres_sql_constructors(
|
|
155
175
|
tree: ast.Module, parents: Mapping[ast.AST, ast.AST], *, node_index: NodeIndex | None = None
|
|
156
176
|
) -> frozenset[_ConstructorImport]:
|
|
@@ -261,13 +281,16 @@ class ComplexPostgresQueryRequiresArchitectureReview(Rule):
|
|
|
261
281
|
autofix=AutofixPolicy.NONE,
|
|
262
282
|
limitations=(
|
|
263
283
|
"Only statically recoverable SQL passed to execute, executemany, fetch, fetchrow, fetchval, or prepare in non-test modules with explicit PostgreSQL imports is analyzed, regardless of module filename.",
|
|
264
|
-
"Execution receivers use a conventional database name or import-proven Psycopg ownership. Native execution additionally follows stable module/local SQL bindings and direct same-class query-forwarding wrappers; unknown receivers abstain.",
|
|
284
|
+
"Execution receivers use a conventional database name or import-proven Psycopg ownership. Conventional names are a heuristic and can warn on unrelated objects with matching methods. Native execution additionally follows stable module/local SQL bindings and direct same-class query-forwarding wrappers; unknown receivers abstain.",
|
|
285
|
+
"Modules mixing SQLite and PostgreSQL abstain for conventional receivers; import-proven Psycopg execution remains eligible.",
|
|
286
|
+
"Stable source-local class construction abstains from conventional receiver inference, including custom delegating instances unless native recovery independently proves execution.",
|
|
265
287
|
"Conventional receivers follow one direct, unambiguous same-scope binding. Native Psycopg recovery may follow bounded stable lexical bindings, including module constants. Unexecuted constants, branch-local assignments, mutable attributes, containers and unknown dynamic composition abstain.",
|
|
266
|
-
"Review covers derived relations in every SELECT (including LATERAL, CTEs, predicates and writes), four explicit or comma joins, six query stages, three joins plus four stages, or 25 projections plus two joins. A self join over the same explicit table also merits review at 16 projected expressions.",
|
|
288
|
+
"Review covers derived relations in every SELECT (including LATERAL, CTEs, predicates and writes), four explicit or comma joins (including parenthesized joined tables), six query stages, three joins plus four stages, or 25 projections plus two joins. A self join over the same explicit table also merits review at 16 projected expressions.",
|
|
267
289
|
"Derived-relation review requires grouping, HAVING or windowing combined with joins or nested reads; expression density is not a separate trigger.",
|
|
268
|
-
"Projection width alone is accepted, and UNION branches are measured independently; execution plans and production cardinality remain authoritative.",
|
|
269
|
-
"ClickHouse and BigQuery syntax
|
|
290
|
+
"Projection width alone is accepted, and UNION branches are measured independently. Set operators do not add read stages beyond their SELECT blocks; execution plans and production cardinality remain authoritative.",
|
|
291
|
+
"Conventional receivers exclude ClickHouse and BigQuery syntax per query, including in mixed-backend modules. Import-proven Psycopg execution uses PostgreSQL parsing even when identifiers or function names overlap another dialect.",
|
|
270
292
|
"The rule does not infer optimizer behavior, materialization, performance, data-model quality, or datastore placement.",
|
|
293
|
+
"SQL parsing and static query recovery abstain on recursion limits; deeply nested source can withhold native recovery for the file.",
|
|
271
294
|
),
|
|
272
295
|
examples=(
|
|
273
296
|
RuleExample(
|
|
@@ -351,12 +374,17 @@ class ComplexPostgresQueryRequiresArchitectureReview(Rule):
|
|
|
351
374
|
tree = context.tree
|
|
352
375
|
if tree is None:
|
|
353
376
|
return []
|
|
354
|
-
|
|
377
|
+
imports = _import_names(tree, node_index=context.node_index)
|
|
378
|
+
if not _has_import_prefix(imports, _POSTGRES_IMPORT_PREFIXES):
|
|
355
379
|
return []
|
|
356
380
|
|
|
357
|
-
diagnostics =
|
|
381
|
+
diagnostics = (
|
|
382
|
+
[]
|
|
383
|
+
if _has_import_prefix(imports, _AMBIGUOUS_RELATIONAL_IMPORT_PREFIXES)
|
|
384
|
+
else _conventional_findings(context, self.code)
|
|
385
|
+
)
|
|
358
386
|
for query in store_queries(context):
|
|
359
|
-
signal = _query_architecture_signal(query.sql, path)
|
|
387
|
+
signal = _query_architecture_signal(query.sql, path, native_postgres=True)
|
|
360
388
|
if signal is None:
|
|
361
389
|
continue
|
|
362
390
|
node = query.node
|
|
@@ -372,7 +400,7 @@ def _conventional_findings(context: PythonFileContext, code: str) -> list[Diagno
|
|
|
372
400
|
tree = context.tree
|
|
373
401
|
if tree is None:
|
|
374
402
|
return []
|
|
375
|
-
query_roots = _query_roots(tree, parents, node_index=context.node_index)
|
|
403
|
+
query_roots = _query_roots(tree, parents, context=context, node_index=context.node_index)
|
|
376
404
|
diagnostics: list[Diagnostic] = []
|
|
377
405
|
consumed: set[int] = set()
|
|
378
406
|
for node in context.nodes(ast.Constant, ast.BinOp, ast.JoinedStr):
|
|
@@ -477,14 +505,13 @@ def _record_sql_constructors(
|
|
|
477
505
|
)
|
|
478
506
|
|
|
479
507
|
|
|
480
|
-
def _query_architecture_signal(text_value: str, path: Path) -> str | None:
|
|
508
|
+
def _query_architecture_signal(text_value: str, path: Path, *, native_postgres: bool = False) -> str | None:
|
|
481
509
|
sql_without_noise = strip_sql_noise(text_value)
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
):
|
|
510
|
+
if _QUERY_SHAPE.search(sql_without_noise) is None:
|
|
511
|
+
return None
|
|
512
|
+
if not native_postgres and (_CLICKHOUSE_SQL.search(sql_without_noise) or _BIGQUERY_SQL.search(sql_without_noise)):
|
|
513
|
+
return None
|
|
514
|
+
if is_store_module(path) and analytical_signal(sql_without_noise) is not None:
|
|
488
515
|
return None
|
|
489
516
|
return _parse_signal(text_value)
|
|
490
517
|
|
|
@@ -512,8 +539,8 @@ def _architecture_signal(statement: exp.Expr) -> str | None:
|
|
|
512
539
|
maximum_joins = _maximum_query_joins(statement)
|
|
513
540
|
if maximum_joins >= _JOIN_LIMIT:
|
|
514
541
|
return f"SELECT block has {maximum_joins} JOINs (4+ explicit JOINs or comma relations)"
|
|
515
|
-
nested_queries = sum(1 for query in statement.walk() if isinstance(query,
|
|
516
|
-
nested_queries -= isinstance(statement,
|
|
542
|
+
nested_queries = sum(1 for query in statement.walk() if isinstance(query, exp.Select))
|
|
543
|
+
nested_queries -= isinstance(_derived_query(statement), exp.Select)
|
|
517
544
|
if (derived_signal := _derived_architecture_signal(statement)) is not None:
|
|
518
545
|
return derived_signal
|
|
519
546
|
if nested_queries >= _STAGE_LIMIT:
|
|
@@ -528,7 +555,7 @@ def _projection_width_signal(statement: exp.Expr) -> str | None:
|
|
|
528
555
|
|
|
529
556
|
for select in statement.find_all(exp.Select):
|
|
530
557
|
projections = tuple(_expression_args(select, "expressions"))
|
|
531
|
-
joins =
|
|
558
|
+
joins = _select_joins(select)
|
|
532
559
|
if len(projections) >= _WIDE_PROJECTION_LIMIT and len(joins) >= _WIDE_PROJECTION_JOIN_LIMIT:
|
|
533
560
|
return f"Joined query block has {len(projections)} projected expressions"
|
|
534
561
|
if len(projections) >= _WIDE_SELF_JOIN_PROJECTION_LIMIT and _self_join(select, joins):
|
|
@@ -559,7 +586,10 @@ def _self_join(select: exp.Select, joins: tuple[exp.Join, ...]) -> bool:
|
|
|
559
586
|
relations.extend(_expression_arg(join, "this") for join in joins)
|
|
560
587
|
seen: set[tuple[str, ...]] = set()
|
|
561
588
|
for relation in relations:
|
|
562
|
-
|
|
589
|
+
base = relation
|
|
590
|
+
while isinstance(base, (exp.Paren, exp.Subquery)):
|
|
591
|
+
base = _expression_arg(base, "this")
|
|
592
|
+
key = relation_key(base)
|
|
563
593
|
if key is None or any(_SQL_HOLE in name for name in key):
|
|
564
594
|
continue
|
|
565
595
|
if key in seen:
|
|
@@ -572,9 +602,7 @@ def _derived_architecture_signal(statement: exp.Expr) -> str | None:
|
|
|
572
602
|
from sqlglot import exp # ruff: ignore[import-outside-top-level] -- keep parser startup off unrelated lint runs
|
|
573
603
|
|
|
574
604
|
for select in statement.find_all(exp.Select):
|
|
575
|
-
for join in
|
|
576
|
-
if not isinstance(join, exp.Join):
|
|
577
|
-
continue
|
|
605
|
+
for join in _select_joins(select):
|
|
578
606
|
if (derived := _derived_query(_expression_arg(join, "this"))) is not None and _has_complex_body(derived):
|
|
579
607
|
return "JOIN-derived query combines grouping, HAVING or windowing with joins or nested reads"
|
|
580
608
|
from_clause = _expression_arg(select, "from_")
|
|
@@ -597,11 +625,11 @@ def _has_complex_body(query: exp.Query) -> bool:
|
|
|
597
625
|
if not isinstance(query, exp.Select):
|
|
598
626
|
return False
|
|
599
627
|
if _has_row_transform(query) and (
|
|
600
|
-
|
|
628
|
+
_select_joins(query) or any(select is not query for select in query.find_all(exp.Select))
|
|
601
629
|
):
|
|
602
630
|
return True
|
|
603
|
-
for relation in query
|
|
604
|
-
if relation.
|
|
631
|
+
for relation in (_expression_arg(query, "from_"), *_select_joins(query)):
|
|
632
|
+
if not isinstance(relation, (exp.From, exp.Join)):
|
|
605
633
|
continue
|
|
606
634
|
child = _derived_query(_expression_arg(relation, "this"))
|
|
607
635
|
if child is not None and (_has_row_transform(child) or _has_complex_body(child)):
|
|
@@ -624,14 +652,26 @@ def _maximum_query_joins(statement: exp.Expr) -> int:
|
|
|
624
652
|
from sqlglot import exp # ruff: ignore[import-outside-top-level] -- keep parser startup off unrelated lint runs
|
|
625
653
|
|
|
626
654
|
return max(
|
|
627
|
-
(
|
|
628
|
-
sum(isinstance(join, exp.Join) for join in _expression_args(select, "joins"))
|
|
629
|
-
for select in statement.find_all(exp.Select)
|
|
630
|
-
),
|
|
655
|
+
(len(_select_joins(select)) for select in statement.find_all(exp.Select)),
|
|
631
656
|
default=0,
|
|
632
657
|
)
|
|
633
658
|
|
|
634
659
|
|
|
660
|
+
def _select_joins(select: exp.Select) -> tuple[exp.Join, ...]:
|
|
661
|
+
from sqlglot import exp # ruff: ignore[import-outside-top-level] -- grouped joined tables retain their SELECT owner.
|
|
662
|
+
|
|
663
|
+
relations = (_expression_arg(select, "from_"), *_expression_args(select, "joins"))
|
|
664
|
+
return tuple(
|
|
665
|
+
node
|
|
666
|
+
for relation in relations
|
|
667
|
+
if relation is not None
|
|
668
|
+
for node in relation.walk(
|
|
669
|
+
prune=lambda node: isinstance(node, (exp.Select, exp.SetOperation, exp.Insert, exp.Update, exp.Delete))
|
|
670
|
+
)
|
|
671
|
+
if isinstance(node, exp.Join)
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
|
|
635
675
|
def _count_import_bindings(
|
|
636
676
|
tree: ast.Module,
|
|
637
677
|
parents: Mapping[ast.AST, ast.AST],
|
|
@@ -671,13 +711,6 @@ def _query_review_guidance(text_value: str) -> str:
|
|
|
671
711
|
)
|
|
672
712
|
|
|
673
713
|
|
|
674
|
-
def _is_postgres_module(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
675
|
-
imports = _import_names(tree, node_index=node_index)
|
|
676
|
-
return _has_import_prefix(imports, _POSTGRES_IMPORT_PREFIXES) and not _has_import_prefix(
|
|
677
|
-
imports, _AMBIGUOUS_RELATIONAL_IMPORT_PREFIXES
|
|
678
|
-
)
|
|
679
|
-
|
|
680
|
-
|
|
681
714
|
def _has_import_prefix(imports: frozenset[str], prefixes: tuple[str, ...]) -> bool:
|
|
682
715
|
return any(name == prefix or name.startswith(f"{prefix}.") for name in imports for prefix in prefixes)
|
|
683
716
|
|
|
@@ -60,15 +60,16 @@ def analytical_signal(sql: str) -> str | None:
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
def _grouped_reporting_signal(sql: str) -> str | None:
|
|
63
|
-
import sqlglot # ruff: ignore[import-outside-top-level] -- parse only aggregate-shaped SQL after cheap gates
|
|
64
63
|
from sqlglot import exp # ruff: ignore[import-outside-top-level] -- paired with lazy parser import
|
|
65
64
|
from sqlglot.errors import SqlglotError # ruff: ignore[import-outside-top-level] -- paired with lazy parser import
|
|
66
65
|
|
|
66
|
+
from sarj_python_lint.rules._postgres import parse_postgres # ruff: ignore[import-outside-top-level] -- unsupported private SQL must not enter dependency logs.
|
|
67
|
+
|
|
67
68
|
if _GROUP_BY.search(sql) is None or _COMMON_AGGREGATE.search(sql) is None:
|
|
68
69
|
return None
|
|
69
70
|
try:
|
|
70
|
-
statements =
|
|
71
|
-
except SqlglotError:
|
|
71
|
+
statements = parse_postgres(_PLACEHOLDER.sub("?", sql))
|
|
72
|
+
except SqlglotError, RecursionError:
|
|
72
73
|
return None
|
|
73
74
|
for statement in statements:
|
|
74
75
|
if not isinstance(statement, exp.Query):
|