sarj-python-lint 1.97.0__tar.gz → 1.99.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.97.0 → sarj_python_lint-1.99.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/__main__.py +1 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_rule_descriptors.py +9 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_registry.py +2 -0
- sarj_python_lint-1.99.0/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +515 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/.gitignore +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/LICENSE +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/README.md +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.97.0 → sarj_python_lint-1.99.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.99.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.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_rule_descriptors.py
RENAMED
|
@@ -8,6 +8,15 @@ 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("service-singleton-delegates-to-bulk"),
|
|
13
|
+
module="sarj_python_lint.rules.service_singleton_delegates_to_bulk",
|
|
14
|
+
class_name="ServiceSingletonDelegatesToBulk",
|
|
15
|
+
code="SARJ556",
|
|
16
|
+
description="Service singleton CRUD operations must delegate to a bulk primitive.",
|
|
17
|
+
aliases=(),
|
|
18
|
+
enabled_by_default=True,
|
|
19
|
+
),
|
|
11
20
|
RuleDescriptor(
|
|
12
21
|
rule_id=RuleId("no-async-client-in-constructor"),
|
|
13
22
|
module="sarj_python_lint.rules.no_async_client_in_constructor",
|
|
@@ -272,6 +272,7 @@ from sarj_python_lint.rules.pytest_fixture_returns_closed_resource import Pytest
|
|
|
272
272
|
from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
|
|
273
273
|
from sarj_python_lint.rules.repeated_unimplemented_test_contract import RepeatedUnimplementedTestContract
|
|
274
274
|
from sarj_python_lint.rules.require_exp_on_jwt_decode import RequireExpOnJwtDecode
|
|
275
|
+
from sarj_python_lint.rules.service_singleton_delegates_to_bulk import ServiceSingletonDelegatesToBulk
|
|
275
276
|
from sarj_python_lint.rules.store_field_update_reuses_upsert import StoreFieldUpdateReusesUpsert
|
|
276
277
|
from sarj_python_lint.rules.store_insert_requires_explicit_conflict_policy import (
|
|
277
278
|
StoreInsertRequiresExplicitConflictPolicy,
|
|
@@ -282,6 +283,7 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
|
|
|
282
283
|
|
|
283
284
|
_AUTHORING_REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
284
285
|
{
|
|
286
|
+
ServiceSingletonDelegatesToBulk.id: ServiceSingletonDelegatesToBulk,
|
|
285
287
|
NoAsyncClientInConstructor.id: NoAsyncClientInConstructor,
|
|
286
288
|
NoDiscardedTaggedOutcome.id: NoDiscardedTaggedOutcome,
|
|
287
289
|
StoreInsertRequiresExplicitConflictPolicy.id: StoreInsertRequiresExplicitConflictPolicy,
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import PurePosixPath
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.rule_base import (
|
|
9
|
+
AutofixPolicy,
|
|
10
|
+
Diagnostic,
|
|
11
|
+
ExampleFile,
|
|
12
|
+
ExampleOutcome,
|
|
13
|
+
Rule,
|
|
14
|
+
RuleCategory,
|
|
15
|
+
RuleDocumentation,
|
|
16
|
+
RuleExample,
|
|
17
|
+
Severity,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._annotation_semantics import scope_bound_names
|
|
20
|
+
from sarj_python_lint.rules._imports import TYPING_SOURCES
|
|
21
|
+
from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
|
|
22
|
+
from sarj_python_lint.rules._store_writes import methods, own_nodes
|
|
23
|
+
from sarj_python_lint.rules.require_port_for_service import is_data_type
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
28
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
type _Method = ast.FunctionDef | ast.AsyncFunctionDef
|
|
32
|
+
_OPERATIONS = frozenset({"get", "create", "insert", "upsert", "update", "delete"})
|
|
33
|
+
_ROLES = ("Service", "Store", "Repository")
|
|
34
|
+
_NEUTRAL_DECORATORS = frozenset(
|
|
35
|
+
{"typing.override", "typing_extensions.override", "typing.final", "typing_extensions.final"}
|
|
36
|
+
)
|
|
37
|
+
_ABSTRACT_DECORATORS = frozenset({"abc.abstractmethod", "typing.overload", "typing_extensions.overload"})
|
|
38
|
+
_NEUTRAL_BASES = frozenset({"abc.ABC", "typing.Generic", "typing_extensions.Generic"})
|
|
39
|
+
_MAX_STATES = 64
|
|
40
|
+
_UNKNOWN = "?"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@final
|
|
44
|
+
class ServiceSingletonDelegatesToBulk(Rule):
|
|
45
|
+
id = "service-singleton-delegates-to-bulk"
|
|
46
|
+
code = "SARJ556"
|
|
47
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
48
|
+
default_level=Severity.ERROR,
|
|
49
|
+
summary="Service singleton CRUD operations must delegate to a bulk primitive.",
|
|
50
|
+
rationale=(
|
|
51
|
+
"A singleton-only API forces callers into serial operations. Separate singleton and bulk implementations "
|
|
52
|
+
"duplicate filtering, validation and mutation behavior; making bulk loop through singleton preserves that "
|
|
53
|
+
"cost and can recurse when the singleton is later made an adapter."
|
|
54
|
+
),
|
|
55
|
+
remediation=(
|
|
56
|
+
"Implement get_many, create_many, insert_many, upsert_many, update_many or delete_many as the primitive. "
|
|
57
|
+
"Delegate the matching singleton through a one-item collection, forwarding shared context and awaiting "
|
|
58
|
+
"async operations. Preserve result projection, missing-item, authorization and transaction semantics; "
|
|
59
|
+
"use a reasoned exact-code suppression for a boundary that cannot support batching."
|
|
60
|
+
),
|
|
61
|
+
category=RuleCategory.ARCHITECTURE,
|
|
62
|
+
autofix=AutofixPolicy.NONE,
|
|
63
|
+
limitations=(
|
|
64
|
+
"Checks module-level Service, Store and Repository classes with concrete instance CRUD methods and at least one input; arbitrary domain verbs are not inferred. Existing get_by_ids bulk reads are accepted alongside get_many.",
|
|
65
|
+
"Tests, fakes, generated/vendor code, stubs, data classes, protocols, static/class methods, abstract/overload declarations and rewriting decorators are excluded.",
|
|
66
|
+
"Same-file inherited methods are resolved. Missing bulk methods are not inferred through unknown external bases. Aliased, rebound, overloaded, descriptor and decorated bulk members are deferred; dynamic dispatch and imported inheritance remain type-checker and review concerns.",
|
|
67
|
+
"Adapters use straight-line calls, scalar and one-item list/tuple/set aliases, and private instance helpers, with a bounded 64-state walk. Complex control flow requires an exact exception or a simple adapter helper.",
|
|
68
|
+
"Requires the original item and shared parameters to reach the bulk call and rejects visible reverse calls through same-class methods. It does not prove database batching, result projection, authorization, atomicity or arbitrary external side effects.",
|
|
69
|
+
),
|
|
70
|
+
examples=(
|
|
71
|
+
RuleExample(
|
|
72
|
+
example_id="missing-bulk-api",
|
|
73
|
+
title="A singleton operation needs a bulk primitive",
|
|
74
|
+
outcome=ExampleOutcome.MATCH,
|
|
75
|
+
files=(
|
|
76
|
+
ExampleFile.python(
|
|
77
|
+
"app/users.py",
|
|
78
|
+
"class UserService:\n async def get(self, key: str):\n return await self.store.get(key)\n",
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
focus_path=PurePosixPath("app/users.py"),
|
|
82
|
+
expected_count=1,
|
|
83
|
+
public=True,
|
|
84
|
+
),
|
|
85
|
+
RuleExample(
|
|
86
|
+
example_id="bulk-first-adapter",
|
|
87
|
+
title="Implement singleton access as a one-item bulk operation",
|
|
88
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
89
|
+
files=(
|
|
90
|
+
ExampleFile.python(
|
|
91
|
+
"app/users.py",
|
|
92
|
+
"class UserService:\n async def get(self, key: str):\n rows = await self.get_many([key])\n return rows[0] if rows else None\n async def get_many(self, keys: list[str]):\n return await self.store.get_many(keys)\n",
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
focus_path=PurePosixPath("app/users.py"),
|
|
96
|
+
expected_count=0,
|
|
97
|
+
public=True,
|
|
98
|
+
),
|
|
99
|
+
RuleExample(
|
|
100
|
+
example_id="inverted-bulk-write",
|
|
101
|
+
scenario="write",
|
|
102
|
+
title="Bulk writes must not call back through the singleton",
|
|
103
|
+
outcome=ExampleOutcome.MATCH,
|
|
104
|
+
files=(
|
|
105
|
+
ExampleFile.python(
|
|
106
|
+
"app/users.py",
|
|
107
|
+
"class UserService:\n def upsert(self, user):\n return self.upsert_many([user])[0]\n def upsert_many(self, users):\n return [self.upsert(user) for user in users]\n",
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
focus_path=PurePosixPath("app/users.py"),
|
|
111
|
+
expected_count=1,
|
|
112
|
+
public=True,
|
|
113
|
+
),
|
|
114
|
+
RuleExample(
|
|
115
|
+
example_id="bulk-first-write",
|
|
116
|
+
scenario="write",
|
|
117
|
+
title="Write through the bulk primitive",
|
|
118
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
119
|
+
files=(
|
|
120
|
+
ExampleFile.python(
|
|
121
|
+
"app/users.py",
|
|
122
|
+
"class UserService:\n def upsert(self, user):\n return self.upsert_many([user])[0]\n def upsert_many(self, users):\n return self.store.upsert_many(users)\n",
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
focus_path=PurePosixPath("app/users.py"),
|
|
126
|
+
expected_count=0,
|
|
127
|
+
public=True,
|
|
128
|
+
),
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
description = documentation.summary
|
|
132
|
+
|
|
133
|
+
@override
|
|
134
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
135
|
+
if (
|
|
136
|
+
context.generated
|
|
137
|
+
or is_test_path(context.path)
|
|
138
|
+
or is_test_support_path(context.path)
|
|
139
|
+
or context.path.suffix == ".pyi"
|
|
140
|
+
):
|
|
141
|
+
return []
|
|
142
|
+
tree = context.tree
|
|
143
|
+
if tree is None:
|
|
144
|
+
return []
|
|
145
|
+
classes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)}
|
|
146
|
+
return [
|
|
147
|
+
diagnostic
|
|
148
|
+
for owner in classes.values()
|
|
149
|
+
if owner.name.endswith(_ROLES)
|
|
150
|
+
and all(
|
|
151
|
+
context.module_imports.resolved_qualified_name(d) in _NEUTRAL_DECORATORS for d in owner.decorator_list
|
|
152
|
+
)
|
|
153
|
+
and not is_data_type(owner)
|
|
154
|
+
and not any(
|
|
155
|
+
context.module_imports.resolves(
|
|
156
|
+
base.value if isinstance(base, ast.Subscript) else base, sources=TYPING_SOURCES, symbol="Protocol"
|
|
157
|
+
)
|
|
158
|
+
for base in owner.bases
|
|
159
|
+
)
|
|
160
|
+
for diagnostic in _owner_diagnostics(owner, classes, context, self.code)
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True, slots=True)
|
|
165
|
+
class _Surface:
|
|
166
|
+
methods: dict[str, _Method]
|
|
167
|
+
uncertain: frozenset[str]
|
|
168
|
+
unknown_base: bool
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass(frozen=True, slots=True)
|
|
172
|
+
class _Call:
|
|
173
|
+
name: str
|
|
174
|
+
bindings: dict[str, str]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _owner_diagnostics(
|
|
178
|
+
owner: ast.ClassDef, classes: dict[str, ast.ClassDef], context: PythonFileContext, code: str
|
|
179
|
+
) -> list[Diagnostic]:
|
|
180
|
+
surface = _inherited_methods(owner, classes, context.module_imports, frozenset())
|
|
181
|
+
diagnostics: list[Diagnostic] = []
|
|
182
|
+
for name, singleton in methods(owner).items():
|
|
183
|
+
if name not in _OPERATIONS or not _concrete_instance(singleton, context.module_imports):
|
|
184
|
+
continue
|
|
185
|
+
problem = _operation_problem(singleton, surface, context.module_imports)
|
|
186
|
+
if problem is not None:
|
|
187
|
+
diagnostics.append(
|
|
188
|
+
Diagnostic(
|
|
189
|
+
path=context.path,
|
|
190
|
+
line=singleton.lineno,
|
|
191
|
+
col=singleton.col_offset + 1,
|
|
192
|
+
code=code,
|
|
193
|
+
severity=Severity.ERROR,
|
|
194
|
+
message=f"`{owner.name}.{name}` {problem} Implement `{name}_many` as the bulk primitive and delegate `{name}` with one item and the same context.",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
return diagnostics
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _concrete_instance(method: _Method, imports: ImportIndex) -> bool:
|
|
201
|
+
parameters = _parameters(method)
|
|
202
|
+
if not parameters or method.args.vararg or method.args.kwarg:
|
|
203
|
+
return False
|
|
204
|
+
if any(imports.resolved_qualified_name(d) not in _NEUTRAL_DECORATORS for d in method.decorator_list):
|
|
205
|
+
return False
|
|
206
|
+
body = [
|
|
207
|
+
s
|
|
208
|
+
for s in method.body
|
|
209
|
+
if not (isinstance(s, ast.Expr) and isinstance(s.value, ast.Constant) and isinstance(s.value.value, str))
|
|
210
|
+
]
|
|
211
|
+
return bool(body) and not all(_stub(statement) for statement in body)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _parameters(method: _Method) -> list[ast.arg]:
|
|
215
|
+
positional = [*method.args.posonlyargs, *method.args.args]
|
|
216
|
+
if not positional or positional[0].arg != "self":
|
|
217
|
+
return []
|
|
218
|
+
return [*positional[1:], *method.args.kwonlyargs]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _stub(statement: ast.stmt) -> bool:
|
|
222
|
+
if isinstance(statement, ast.Pass):
|
|
223
|
+
return True
|
|
224
|
+
if isinstance(statement, ast.Expr) and isinstance(statement.value, ast.Constant):
|
|
225
|
+
return statement.value.value is Ellipsis
|
|
226
|
+
if isinstance(statement, ast.Raise):
|
|
227
|
+
value = statement.exc.func if isinstance(statement.exc, ast.Call) else statement.exc
|
|
228
|
+
return isinstance(value, ast.Name) and value.id == "NotImplementedError"
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _inherited_methods(
|
|
233
|
+
owner: ast.ClassDef, classes: dict[str, ast.ClassDef], imports: ImportIndex, seen: frozenset[str]
|
|
234
|
+
) -> _Surface:
|
|
235
|
+
if owner.name in seen:
|
|
236
|
+
return _Surface({}, frozenset(), unknown_base=True)
|
|
237
|
+
result: dict[str, _Method] = {}
|
|
238
|
+
uncertain: set[str] = set()
|
|
239
|
+
unknown = False
|
|
240
|
+
for base in reversed(owner.bases):
|
|
241
|
+
target = base.value if isinstance(base, ast.Subscript) else base
|
|
242
|
+
if isinstance(target, ast.Name) and target.id in classes:
|
|
243
|
+
inherited = _inherited_methods(classes[target.id], classes, imports, seen | {owner.name})
|
|
244
|
+
for name in inherited.uncertain:
|
|
245
|
+
result.pop(name, None)
|
|
246
|
+
result.update(inherited.methods)
|
|
247
|
+
uncertain.difference_update(inherited.methods)
|
|
248
|
+
uncertain.update(inherited.uncertain)
|
|
249
|
+
unknown |= inherited.unknown_base
|
|
250
|
+
elif imports.resolved_qualified_name(target) not in _NEUTRAL_BASES and not (
|
|
251
|
+
isinstance(target, ast.Name) and target.id == "object" and imports.builtin_is_unshadowed("object")
|
|
252
|
+
):
|
|
253
|
+
unknown = True
|
|
254
|
+
declared = methods(owner)
|
|
255
|
+
bound = _member_bindings(owner)
|
|
256
|
+
for name in bound:
|
|
257
|
+
result.pop(name, None)
|
|
258
|
+
result.update(declared)
|
|
259
|
+
uncertain.difference_update(bound)
|
|
260
|
+
uncertain.update(bound - declared.keys() - _literal_members(owner))
|
|
261
|
+
return _Surface(result, frozenset(uncertain), unknown)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _member_bindings(owner: ast.ClassDef) -> set[str]:
|
|
265
|
+
bound = scope_bound_names(owner.body)
|
|
266
|
+
bound.update(
|
|
267
|
+
node.attr
|
|
268
|
+
for method in owner.body
|
|
269
|
+
if isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
270
|
+
for node in own_nodes(method)
|
|
271
|
+
if isinstance(node, ast.Attribute)
|
|
272
|
+
and isinstance(node.ctx, ast.Store | ast.Del)
|
|
273
|
+
and isinstance(node.value, ast.Name)
|
|
274
|
+
and node.value.id == "self"
|
|
275
|
+
)
|
|
276
|
+
return bound
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _literal_members(owner: ast.ClassDef) -> set[str]:
|
|
280
|
+
literals: set[str] = set()
|
|
281
|
+
for statement in owner.body:
|
|
282
|
+
literals.difference_update(scope_bound_names([statement]))
|
|
283
|
+
if not isinstance(statement, ast.Assign | ast.AnnAssign) or not isinstance(
|
|
284
|
+
statement.value, ast.Constant | ast.List | ast.Tuple | ast.Set | ast.Dict
|
|
285
|
+
):
|
|
286
|
+
continue
|
|
287
|
+
targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
|
|
288
|
+
literals.update(target.id for target in targets if isinstance(target, ast.Name))
|
|
289
|
+
return literals
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _operation_problem(singleton: _Method, surface: _Surface, imports: ImportIndex) -> str | None:
|
|
293
|
+
bulk_name = f"{singleton.name}_many"
|
|
294
|
+
names = (bulk_name, "get_by_ids") if singleton.name == "get" else (bulk_name,)
|
|
295
|
+
if any(candidate in surface.uncertain for candidate in names):
|
|
296
|
+
return None
|
|
297
|
+
bulks = [surface.methods[candidate] for candidate in names if candidate in surface.methods]
|
|
298
|
+
if not bulks:
|
|
299
|
+
return None if surface.unknown_base else "has no declared bulk operation."
|
|
300
|
+
if any(not _supported_bulk(bulk, imports) for bulk in bulks):
|
|
301
|
+
return None
|
|
302
|
+
if any(_reaches(bulk.name, singleton.name, surface.methods) for bulk in bulks):
|
|
303
|
+
return "has a bulk operation that calls back through the singleton."
|
|
304
|
+
problems = [_adapter_problem(singleton, bulk, surface.methods, imports) for bulk in bulks]
|
|
305
|
+
return None if None in problems else problems[0]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _supported_bulk(bulk: _Method, imports: ImportIndex) -> bool:
|
|
309
|
+
return (
|
|
310
|
+
not bulk.args.vararg
|
|
311
|
+
and not bulk.args.kwarg
|
|
312
|
+
and all(
|
|
313
|
+
imports.resolved_qualified_name(d) in _NEUTRAL_DECORATORS | _ABSTRACT_DECORATORS
|
|
314
|
+
for d in bulk.decorator_list
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _adapter_problem(
|
|
320
|
+
singleton: _Method, bulk: _Method, available: dict[str, _Method], imports: ImportIndex
|
|
321
|
+
) -> str | None:
|
|
322
|
+
if not _parameters(bulk):
|
|
323
|
+
return "has no instance bulk operation accepting items."
|
|
324
|
+
if not _concrete_instance(bulk, imports) and not any(
|
|
325
|
+
imports.resolved_qualified_name(d) == "abc.abstractmethod" for d in bulk.decorator_list
|
|
326
|
+
):
|
|
327
|
+
return "has only an unimplemented bulk stub."
|
|
328
|
+
bindings = {p.arg: f"input:{p.arg}" for p in _parameters(singleton)}
|
|
329
|
+
if not _delegates(singleton, bulk, available, bindings):
|
|
330
|
+
return "does not delegate its original item and shared context to the bulk operation."
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _reaches(start: str, target: str, available: dict[str, _Method]) -> bool:
|
|
335
|
+
pending = [start]
|
|
336
|
+
seen: set[str] = set()
|
|
337
|
+
while pending:
|
|
338
|
+
name = pending.pop()
|
|
339
|
+
if name in seen:
|
|
340
|
+
continue
|
|
341
|
+
seen.add(name)
|
|
342
|
+
for node in own_nodes(available[name]):
|
|
343
|
+
if not isinstance(node, ast.Call):
|
|
344
|
+
continue
|
|
345
|
+
called = _self_call(node)
|
|
346
|
+
if called is None:
|
|
347
|
+
continue
|
|
348
|
+
if called == target:
|
|
349
|
+
return True
|
|
350
|
+
if called in available:
|
|
351
|
+
pending.append(called)
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _self_call(call: ast.Call) -> str | None:
|
|
356
|
+
if isinstance(call.func, ast.Attribute) and isinstance(call.func.value, ast.Name) and call.func.value.id == "self":
|
|
357
|
+
return call.func.attr
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _symbol(value: ast.expr, symbols: dict[str, str]) -> str:
|
|
362
|
+
match value:
|
|
363
|
+
case ast.Name(id=name):
|
|
364
|
+
return symbols.get(name, _UNKNOWN)
|
|
365
|
+
case ast.List() | ast.Tuple() | ast.Set():
|
|
366
|
+
items = _collection_symbols(value, symbols)
|
|
367
|
+
if items is None or len(items) != 1:
|
|
368
|
+
return _UNKNOWN
|
|
369
|
+
item = items[0]
|
|
370
|
+
return f"one:{item}" if item != _UNKNOWN else _UNKNOWN
|
|
371
|
+
case _:
|
|
372
|
+
return _UNKNOWN
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _collection_symbols(value: ast.List | ast.Tuple | ast.Set, symbols: dict[str, str]) -> list[str] | None:
|
|
376
|
+
items: list[str] = []
|
|
377
|
+
for element in value.elts:
|
|
378
|
+
if not isinstance(element, ast.Starred):
|
|
379
|
+
items.append(_symbol(element, symbols))
|
|
380
|
+
continue
|
|
381
|
+
expanded = element.value
|
|
382
|
+
if isinstance(expanded, ast.List | ast.Tuple | ast.Set) and not expanded.elts:
|
|
383
|
+
continue
|
|
384
|
+
symbol = _symbol(expanded, symbols)
|
|
385
|
+
if not symbol.startswith("one:"):
|
|
386
|
+
return None
|
|
387
|
+
items.append(symbol.removeprefix("one:"))
|
|
388
|
+
return items
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _delegates(singleton: _Method, bulk: _Method, available: dict[str, _Method], initial: dict[str, str]) -> bool:
|
|
392
|
+
pending = [_Call(singleton.name, initial)]
|
|
393
|
+
seen: set[tuple[str, tuple[tuple[str, str], ...]]] = set()
|
|
394
|
+
inputs = frozenset(initial.values())
|
|
395
|
+
while pending and len(seen) < _MAX_STATES:
|
|
396
|
+
candidate = pending.pop()
|
|
397
|
+
state = candidate.name, tuple(sorted(candidate.bindings.items()))
|
|
398
|
+
if state in seen:
|
|
399
|
+
continue
|
|
400
|
+
seen.add(state)
|
|
401
|
+
for call in _method_calls(available[candidate.name], available, candidate.bindings):
|
|
402
|
+
if call.name == bulk.name and _matches(call.bindings, inputs):
|
|
403
|
+
return True
|
|
404
|
+
if call.name.startswith("_"):
|
|
405
|
+
pending.append(call)
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _matches(bindings: dict[str, str], inputs: frozenset[str]) -> bool:
|
|
410
|
+
wrapped = [value.removeprefix("one:") for value in bindings.values() if value.startswith("one:")]
|
|
411
|
+
if len(wrapped) != 1 or wrapped[0] not in inputs:
|
|
412
|
+
return False
|
|
413
|
+
for value in inputs - {wrapped[0]}:
|
|
414
|
+
name = value.removeprefix("input:")
|
|
415
|
+
if value not in bindings.values():
|
|
416
|
+
return False
|
|
417
|
+
if name in bindings and bindings[name] != value:
|
|
418
|
+
return False
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _method_calls(method: _Method, available: dict[str, _Method], bindings: dict[str, str]) -> list[_Call]:
|
|
423
|
+
calls: list[_Call] = []
|
|
424
|
+
symbols = {**bindings, "self": "receiver:self"}
|
|
425
|
+
for statement in method.body:
|
|
426
|
+
if isinstance(statement, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
|
|
427
|
+
continue
|
|
428
|
+
if not isinstance(statement, ast.Assign | ast.AnnAssign | ast.Expr | ast.Return | ast.Raise | ast.Pass):
|
|
429
|
+
break
|
|
430
|
+
calls.extend(_bound_calls(statement, available, symbols))
|
|
431
|
+
_assignment(statement, symbols)
|
|
432
|
+
if isinstance(statement, ast.Return | ast.Raise):
|
|
433
|
+
break
|
|
434
|
+
return calls
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _bound_calls(statement: ast.stmt, available: dict[str, _Method], symbols: dict[str, str]) -> list[_Call]:
|
|
438
|
+
nodes = _eager_nodes(statement)
|
|
439
|
+
awaited = {id(n.value) for n in nodes if isinstance(n, ast.Await)}
|
|
440
|
+
calls: list[_Call] = []
|
|
441
|
+
for node in nodes:
|
|
442
|
+
if not isinstance(node, ast.Call):
|
|
443
|
+
continue
|
|
444
|
+
called = _self_call(node)
|
|
445
|
+
if called is None:
|
|
446
|
+
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
|
|
447
|
+
_invalidate(node.func.value, symbols)
|
|
448
|
+
continue
|
|
449
|
+
if symbols.get("self") != "receiver:self":
|
|
450
|
+
continue
|
|
451
|
+
target = available.get(called)
|
|
452
|
+
if target is None or (isinstance(target, ast.AsyncFunctionDef) and id(node) not in awaited):
|
|
453
|
+
continue
|
|
454
|
+
bindings = _bind(node, target, symbols)
|
|
455
|
+
if bindings is not None:
|
|
456
|
+
calls.append(_Call(called, bindings))
|
|
457
|
+
return calls
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _eager_nodes(statement: ast.stmt) -> list[ast.AST]:
|
|
461
|
+
nodes: list[ast.AST] = []
|
|
462
|
+
pending: list[ast.AST] = [statement]
|
|
463
|
+
while pending:
|
|
464
|
+
node = pending.pop()
|
|
465
|
+
if isinstance(node, ast.Lambda | ast.GeneratorExp | ast.ListComp | ast.SetComp | ast.DictComp | ast.IfExp):
|
|
466
|
+
continue
|
|
467
|
+
nodes.append(node)
|
|
468
|
+
children = [node.values[0]] if isinstance(node, ast.BoolOp) else list(ast.iter_child_nodes(node))
|
|
469
|
+
pending.extend(reversed(children))
|
|
470
|
+
return nodes
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _bind(call: ast.Call, method: _Method, symbols: dict[str, str]) -> dict[str, str] | None:
|
|
474
|
+
positional = [*method.args.posonlyargs, *method.args.args][1:]
|
|
475
|
+
parameters = _parameters(method)
|
|
476
|
+
if len(call.args) > len(positional) or any(isinstance(arg, ast.Starred) for arg in call.args):
|
|
477
|
+
return None
|
|
478
|
+
result = {p.arg: _symbol(arg, symbols) for p, arg in zip(positional, call.args, strict=False)}
|
|
479
|
+
names = {p.arg for p in parameters}
|
|
480
|
+
positional_only = {p.arg for p in method.args.posonlyargs}
|
|
481
|
+
for keyword in call.keywords:
|
|
482
|
+
name = keyword.arg
|
|
483
|
+
if name is None or name not in names or name in result or name in positional_only:
|
|
484
|
+
return None
|
|
485
|
+
result[name] = _symbol(keyword.value, symbols)
|
|
486
|
+
required = positional[: -len(method.args.defaults)] if method.args.defaults else positional
|
|
487
|
+
required += [
|
|
488
|
+
p for p, default in zip(method.args.kwonlyargs, method.args.kw_defaults, strict=True) if default is None
|
|
489
|
+
]
|
|
490
|
+
if any(p.arg not in result for p in required):
|
|
491
|
+
return None
|
|
492
|
+
return result
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _invalidate(target: ast.expr, symbols: dict[str, str]) -> None:
|
|
496
|
+
if isinstance(target, ast.Subscript):
|
|
497
|
+
target = target.value
|
|
498
|
+
if not isinstance(target, ast.Name):
|
|
499
|
+
return
|
|
500
|
+
value = symbols.get(target.id, _UNKNOWN)
|
|
501
|
+
for name, symbol in tuple(symbols.items()):
|
|
502
|
+
if symbol == value:
|
|
503
|
+
symbols[name] = _UNKNOWN
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _assignment(statement: ast.stmt, symbols: dict[str, str]) -> None:
|
|
507
|
+
if not isinstance(statement, ast.Assign | ast.AnnAssign) or statement.value is None:
|
|
508
|
+
return
|
|
509
|
+
value = _symbol(statement.value, symbols)
|
|
510
|
+
targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
|
|
511
|
+
for target in targets:
|
|
512
|
+
if isinstance(target, ast.Name):
|
|
513
|
+
symbols[target.id] = value
|
|
514
|
+
else:
|
|
515
|
+
_invalidate(target, symbols)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_aliases.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_annotations.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_comments.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_declared_tests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_exhaustiveness.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_parameters.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_repeated.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_security.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_autofix_sql_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/_prepared_sources.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/application_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.97.0 → sarj_python_lint-1.99.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|