sarj-python-lint 1.101.11__tar.gz → 1.104.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.101.11 → sarj_python_lint-1.104.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_rule_descriptors.py +1 -1
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_store_writes.py +390 -54
- sarj_python_lint-1.104.0/src/sarj_python_lint/rules/prefer_bulk_upsert.py +401 -0
- sarj_python_lint-1.104.0/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +928 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +80 -8
- sarj_python_lint-1.101.11/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -139
- sarj_python_lint-1.101.11/src/sarj_python_lint/rules/service_singleton_delegates_to_bulk.py +0 -515
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/.gitignore +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/LICENSE +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/README.md +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_prepared_sources.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rule_metadata.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_async_lifecycle.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_binding_inventory.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_numeric_proof.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_postgres.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_postgres_ctas.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_runtime_registry.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_store_sql.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_executor_shutdown.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_async_client_in_constructor.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_custom_control_flow_exception.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_discarded_tagged_outcome.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_generic_json_parser.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_mutating_input_with_result.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_recording_store.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_attribute_unpacking.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_redundant_sql_alias.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_test_comments.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_application_model_joins.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_optional_composite.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_runtime_validation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_cli.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_mapping_get.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_constrained_api_integer.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_insert_requires_explicit_conflict_policy.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/store_mutation_command_fanout.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.101.11 → sarj_python_lint-1.104.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.104.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.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/_rule_descriptors.py
RENAMED
|
@@ -256,7 +256,7 @@ DESCRIPTORS: tuple[RuleDescriptor, ...] = (
|
|
|
256
256
|
module="sarj_python_lint.rules.prefer_bulk_upsert",
|
|
257
257
|
class_name="PreferBulkUpsert",
|
|
258
258
|
code="SARJ516",
|
|
259
|
-
description="Use
|
|
259
|
+
description="Use declared compatible bulk APIs for repeated singleton reads and writes.",
|
|
260
260
|
aliases=(),
|
|
261
261
|
enabled_by_default=True,
|
|
262
262
|
),
|
{sarj_python_lint-1.101.11 → sarj_python_lint-1.104.0}/src/sarj_python_lint/rules/_store_writes.py
RENAMED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
4
5
|
from types import MappingProxyType
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
6
7
|
|
|
7
8
|
import sqlglot
|
|
8
9
|
from sqlglot import exp
|
|
9
10
|
from sqlglot.errors import SqlglotError
|
|
10
11
|
|
|
11
|
-
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, scope_bound_names
|
|
12
|
+
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics, bit_or_members, scope_bound_names
|
|
12
13
|
from sarj_python_lint.rules._ast_index import walk
|
|
13
14
|
from sarj_python_lint.rules._paths import is_generated
|
|
14
15
|
from sarj_python_lint.rules._project_index import ProjectIndexSet
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
|
-
from collections.abc import Iterator
|
|
19
|
+
from collections.abc import Iterable, Iterator
|
|
19
20
|
|
|
20
21
|
from sarj_python_lint._file_context import PythonFileContext
|
|
21
22
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
@@ -23,6 +24,13 @@ if TYPE_CHECKING:
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
type Function = ast.FunctionDef | ast.AsyncFunctionDef
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _MethodPair(NamedTuple):
|
|
30
|
+
singleton: Function
|
|
31
|
+
bulk: Function
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
_COLLECTIONS = frozenset(
|
|
27
35
|
{
|
|
28
36
|
"collections.abc.Collection",
|
|
@@ -35,6 +43,26 @@ _COLLECTIONS = frozenset(
|
|
|
35
43
|
}
|
|
36
44
|
)
|
|
37
45
|
_NEUTRAL_DECORATORS = frozenset({"abc.abstractmethod", "typing.override", "typing_extensions.override"})
|
|
46
|
+
_READ_SEQUENCES = frozenset({"collections.abc.Sequence", "typing.Sequence", "typing.List"})
|
|
47
|
+
_READ_MAPPINGS = frozenset({"collections.abc.Mapping", "typing.Mapping", "typing.Dict"})
|
|
48
|
+
_STORE_MEMBERS = frozenset(
|
|
49
|
+
{
|
|
50
|
+
"upsert",
|
|
51
|
+
"upsert_many",
|
|
52
|
+
"create",
|
|
53
|
+
"create_many",
|
|
54
|
+
"insert",
|
|
55
|
+
"insert_many",
|
|
56
|
+
"update",
|
|
57
|
+
"update_many",
|
|
58
|
+
"delete",
|
|
59
|
+
"delete_many",
|
|
60
|
+
"get",
|
|
61
|
+
"get_many",
|
|
62
|
+
"get_by_ids",
|
|
63
|
+
"__class__",
|
|
64
|
+
}
|
|
65
|
+
)
|
|
38
66
|
|
|
39
67
|
|
|
40
68
|
def method_parameters(method: Function) -> list[ast.arg] | None:
|
|
@@ -66,11 +94,228 @@ def methods(owner: ast.ClassDef) -> dict[str, Function]:
|
|
|
66
94
|
return {name: method for name, method in result.items() if name not in ambiguous}
|
|
67
95
|
|
|
68
96
|
|
|
97
|
+
def instance_member_bindings(owner: ast.ClassDef, context: PythonFileContext) -> set[str]:
|
|
98
|
+
bindings: set[str] = set()
|
|
99
|
+
for method in owner.body:
|
|
100
|
+
if not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
101
|
+
continue
|
|
102
|
+
for node in own_nodes(method):
|
|
103
|
+
if isinstance(node, ast.Attribute) and _writes_self_attribute(node, node.attr):
|
|
104
|
+
bindings.add(node.attr)
|
|
105
|
+
elif (member := _builtin_member_binding(node, method, context)) is not None:
|
|
106
|
+
bindings.add(member)
|
|
107
|
+
return bindings
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _builtin_member_binding(node: ast.AST, method: Function, context: PythonFileContext) -> str | None:
|
|
111
|
+
if not isinstance(node, ast.Call) or node.keywords:
|
|
112
|
+
return None
|
|
113
|
+
provenance = context.static_resource_provenance
|
|
114
|
+
if provenance.wildcard or provenance.dynamic_namespace:
|
|
115
|
+
return None
|
|
116
|
+
name = provenance.runtime_import_name(node.func)
|
|
117
|
+
if (
|
|
118
|
+
isinstance(node.func, ast.Name)
|
|
119
|
+
and node.func.id in {"setattr", "delattr"}
|
|
120
|
+
and context.imports.builtin_is_unshadowed(node.func.id)
|
|
121
|
+
and provenance.runtime_binding(node.func.id, node.func) is None
|
|
122
|
+
and provenance.builtin_namespace_untouched
|
|
123
|
+
):
|
|
124
|
+
name = "builtins." + node.func.id
|
|
125
|
+
arity = {"builtins.setattr": 3, "builtins.delattr": 2}.get(name or "")
|
|
126
|
+
if arity is None or len(node.args) != arity:
|
|
127
|
+
return None
|
|
128
|
+
receiver, member = node.args[:2]
|
|
129
|
+
if not isinstance(member, ast.Constant) or not isinstance(member.value, str):
|
|
130
|
+
return None
|
|
131
|
+
positional = [*method.args.posonlyargs, *method.args.args]
|
|
132
|
+
if (
|
|
133
|
+
not positional
|
|
134
|
+
or positional[0].arg != "self"
|
|
135
|
+
or not isinstance(receiver, ast.Name)
|
|
136
|
+
or provenance.runtime_binding(receiver.id, receiver) is not positional[0]
|
|
137
|
+
):
|
|
138
|
+
return None
|
|
139
|
+
return member.value
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def decorator_name(
|
|
143
|
+
expression: ast.expr, declaration: Function | ast.ClassDef, owner: ast.ClassDef | ast.Module, imports: ImportIndex
|
|
144
|
+
) -> str | None:
|
|
145
|
+
root = expression
|
|
146
|
+
while isinstance(root, ast.Attribute):
|
|
147
|
+
root = root.value
|
|
148
|
+
if not isinstance(root, ast.Name):
|
|
149
|
+
return None
|
|
150
|
+
for statement in owner.body:
|
|
151
|
+
if statement is declaration:
|
|
152
|
+
return imports.resolved_qualified_name(expression)
|
|
153
|
+
if _evaluation_writes(statement, root.id, names=isinstance(owner, ast.ClassDef)):
|
|
154
|
+
return None
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _evaluation_writes(statement: ast.stmt, name: str, *, names: bool) -> bool:
|
|
159
|
+
pending: list[tuple[ast.AST, bool]] = [(statement, names)]
|
|
160
|
+
while pending:
|
|
161
|
+
node, names = pending.pop()
|
|
162
|
+
if _writes_decorator_root(node, name, names=names):
|
|
163
|
+
return True
|
|
164
|
+
pending.extend(_evaluation_children(node, names=names))
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _writes_decorator_root(node: ast.AST, name: str, *, names: bool) -> bool:
|
|
169
|
+
match node:
|
|
170
|
+
case ast.Name(id=bound, ctx=ast.Store() | ast.Del()) if names:
|
|
171
|
+
return bound == name
|
|
172
|
+
case ast.Attribute(ctx=ast.Store() | ast.Del()):
|
|
173
|
+
root = node.value
|
|
174
|
+
while isinstance(root, ast.Attribute):
|
|
175
|
+
root = root.value
|
|
176
|
+
return isinstance(root, ast.Name) and root.id == name
|
|
177
|
+
case (
|
|
178
|
+
ast.FunctionDef()
|
|
179
|
+
| ast.AsyncFunctionDef()
|
|
180
|
+
| ast.ClassDef()
|
|
181
|
+
| ast.ExceptHandler()
|
|
182
|
+
| ast.MatchAs()
|
|
183
|
+
| ast.MatchStar()
|
|
184
|
+
) if names:
|
|
185
|
+
return node.name == name
|
|
186
|
+
case ast.Import() | ast.ImportFrom() if names:
|
|
187
|
+
return any((alias.asname or alias.name.partition(".")[0]) == name for alias in node.names)
|
|
188
|
+
case ast.Global() | ast.Nonlocal():
|
|
189
|
+
return name in node.names
|
|
190
|
+
case ast.MatchMapping() if names:
|
|
191
|
+
return node.rest == name
|
|
192
|
+
case _:
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _evaluation_children(node: ast.AST, *, names: bool) -> Iterable[tuple[ast.AST, bool]]:
|
|
197
|
+
match node:
|
|
198
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef():
|
|
199
|
+
expressions = [*node.decorator_list, *node.args.defaults]
|
|
200
|
+
expressions.extend(default for default in node.args.kw_defaults if default is not None)
|
|
201
|
+
case ast.Lambda():
|
|
202
|
+
expressions = list(node.args.defaults)
|
|
203
|
+
expressions.extend(default for default in node.args.kw_defaults if default is not None)
|
|
204
|
+
case ast.ClassDef():
|
|
205
|
+
expressions = [*node.decorator_list, *node.bases, *(keyword.value for keyword in node.keywords)]
|
|
206
|
+
return [(child, names) for child in expressions] + [(child, False) for child in node.body]
|
|
207
|
+
case ast.Import() | ast.ImportFrom() | ast.AnnAssign(value=None):
|
|
208
|
+
return []
|
|
209
|
+
case ast.comprehension():
|
|
210
|
+
expressions = [node.iter, *node.ifs]
|
|
211
|
+
case _:
|
|
212
|
+
return [(child, names) for child in ast.iter_child_nodes(node)]
|
|
213
|
+
return [(child, names) for child in expressions]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _decorator_imports(context: PythonFileContext, owner: ast.ClassDef) -> ImportIndex:
|
|
217
|
+
imports = context.module_imports
|
|
218
|
+
if context.tree is None:
|
|
219
|
+
return imports
|
|
220
|
+
preceding = context.tree.body[: context.tree.body.index(owner)]
|
|
221
|
+
return replace(
|
|
222
|
+
imports,
|
|
223
|
+
bindings=MappingProxyType(
|
|
224
|
+
{
|
|
225
|
+
name: target
|
|
226
|
+
for name, target in imports.bindings.items()
|
|
227
|
+
if not any(_evaluation_writes(statement, name, names=False) for statement in preceding)
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
69
233
|
def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Function] | None:
|
|
234
|
+
return compatible_collection_pair(owner, imports, "upsert", "upsert_many")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def compatible_collection_pair(
|
|
238
|
+
owner: ast.ClassDef, imports: ImportIndex, singleton_name: str, bulk_name: str
|
|
239
|
+
) -> _MethodPair | None:
|
|
240
|
+
pair = _method_pair(owner, imports, singleton_name, bulk_name)
|
|
241
|
+
if pair is None:
|
|
242
|
+
return None
|
|
243
|
+
single, bulk = pair
|
|
244
|
+
one, many = method_parameters(single), method_parameters(bulk)
|
|
245
|
+
if one is None or many is None:
|
|
246
|
+
return None
|
|
247
|
+
item = collection_item(many[0].annotation, imports)
|
|
248
|
+
result = collection_item(bulk.returns, imports)
|
|
249
|
+
if (
|
|
250
|
+
item is None
|
|
251
|
+
or result is None
|
|
252
|
+
or not same_type(one[0].annotation, item)
|
|
253
|
+
or not same_type(single.returns, result)
|
|
254
|
+
):
|
|
255
|
+
return None
|
|
256
|
+
return pair
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def read_pair(owner: ast.ClassDef, imports: ImportIndex, bulk_name: str) -> _MethodPair | None:
|
|
260
|
+
pair = _method_pair(owner, imports, "get", bulk_name)
|
|
261
|
+
if pair is None:
|
|
262
|
+
return None
|
|
263
|
+
single, bulk = pair
|
|
264
|
+
one, many = method_parameters(single), method_parameters(bulk)
|
|
265
|
+
if one is None or many is None:
|
|
266
|
+
return None
|
|
267
|
+
item = collection_item(many[0].annotation, imports)
|
|
268
|
+
result = _read_result(bulk.returns, one[0].annotation, imports)
|
|
269
|
+
if (
|
|
270
|
+
item is None
|
|
271
|
+
or result is None
|
|
272
|
+
or not same_type(one[0].annotation, item)
|
|
273
|
+
or not same_type(_read_scalar(single.returns, imports), result)
|
|
274
|
+
):
|
|
275
|
+
return None
|
|
276
|
+
return pair
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _read_scalar(annotation: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
|
|
280
|
+
annotation = AnnotationSemantics.parse(annotation)
|
|
281
|
+
if isinstance(annotation, ast.Subscript) and imports.resolved_qualified_name(annotation.value) in {
|
|
282
|
+
"typing.Optional",
|
|
283
|
+
"typing_extensions.Optional",
|
|
284
|
+
}:
|
|
285
|
+
return annotation.slice if not isinstance(annotation.slice, ast.Tuple) else None
|
|
286
|
+
if isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
|
|
287
|
+
match bit_or_members(annotation):
|
|
288
|
+
case [value, ast.Constant(value=None)] | [ast.Constant(value=None), value]:
|
|
289
|
+
return None if isinstance(value, ast.Constant) and value.value is None else value
|
|
290
|
+
case _:
|
|
291
|
+
return None
|
|
292
|
+
return None if isinstance(annotation, ast.Constant) and annotation.value is None else annotation
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _read_result(annotation: ast.expr | None, key: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
|
|
296
|
+
annotation = AnnotationSemantics.parse(annotation)
|
|
297
|
+
if not isinstance(annotation, ast.Subscript):
|
|
298
|
+
return None
|
|
299
|
+
container = annotation.value
|
|
300
|
+
name = imports.resolved_qualified_name(container)
|
|
301
|
+
if isinstance(container, ast.Name) and imports.builtin_is_unshadowed(container.id):
|
|
302
|
+
name = "builtins." + container.id
|
|
303
|
+
if name in {*_READ_SEQUENCES, "builtins.list"}:
|
|
304
|
+
return annotation.slice if not isinstance(annotation.slice, ast.Tuple) else None
|
|
305
|
+
if name in {*_READ_MAPPINGS, "builtins.dict"}:
|
|
306
|
+
match annotation.slice:
|
|
307
|
+
case ast.Tuple(elts=[mapping_key, value]) if same_type(key, mapping_key):
|
|
308
|
+
return value
|
|
309
|
+
case _:
|
|
310
|
+
pass
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _method_pair(owner: ast.ClassDef, imports: ImportIndex, singleton_name: str, bulk_name: str) -> _MethodPair | None:
|
|
70
315
|
if owner.keywords:
|
|
71
316
|
return None
|
|
72
317
|
declared = methods(owner)
|
|
73
|
-
single, bulk = declared.get(
|
|
318
|
+
single, bulk = declared.get(singleton_name), declared.get(bulk_name)
|
|
74
319
|
if (
|
|
75
320
|
single is None
|
|
76
321
|
or bulk is None
|
|
@@ -78,7 +323,7 @@ def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Fu
|
|
|
78
323
|
):
|
|
79
324
|
return None
|
|
80
325
|
if any(
|
|
81
|
-
|
|
326
|
+
decorator_name(decorator, method, owner, imports) not in _NEUTRAL_DECORATORS
|
|
82
327
|
for method in (single, bulk)
|
|
83
328
|
for decorator in method.decorator_list
|
|
84
329
|
):
|
|
@@ -86,16 +331,7 @@ def upsert_pair(owner: ast.ClassDef, imports: ImportIndex) -> tuple[Function, Fu
|
|
|
86
331
|
one, many = method_parameters(single), method_parameters(bulk)
|
|
87
332
|
if one is None or many is None or len(one) != 1 or len(many) != 1:
|
|
88
333
|
return None
|
|
89
|
-
|
|
90
|
-
result = collection_item(bulk.returns, imports)
|
|
91
|
-
if (
|
|
92
|
-
item is None
|
|
93
|
-
or result is None
|
|
94
|
-
or not same_type(one[0].annotation, item)
|
|
95
|
-
or not same_type(single.returns, result)
|
|
96
|
-
):
|
|
97
|
-
return None
|
|
98
|
-
return single, bulk
|
|
334
|
+
return _MethodPair(single, bulk)
|
|
99
335
|
|
|
100
336
|
|
|
101
337
|
def collection_item(annotation: ast.expr | None, imports: ImportIndex) -> ast.expr | None:
|
|
@@ -134,15 +370,7 @@ def own_nodes(node: ast.AST) -> Iterator[ast.AST]:
|
|
|
134
370
|
def discarded_call(loop: ast.For) -> tuple[ast.Call, bool] | None:
|
|
135
371
|
if loop.orelse or len(loop.body) != 1 or not isinstance(loop.body[0], ast.Expr):
|
|
136
372
|
return None
|
|
137
|
-
if (
|
|
138
|
-
isinstance(loop.iter, ast.List | ast.Tuple | ast.Set)
|
|
139
|
-
and len(loop.iter.elts) <= 1
|
|
140
|
-
and not any(isinstance(item, ast.Starred) for item in loop.iter.elts)
|
|
141
|
-
):
|
|
142
|
-
return None
|
|
143
|
-
if isinstance(loop.iter, ast.Dict) and len(loop.iter.keys) <= 1 and None not in loop.iter.keys:
|
|
144
|
-
return None
|
|
145
|
-
if isinstance(loop.iter, ast.Constant) and isinstance(loop.iter.value, str | bytes) and len(loop.iter.value) <= 1:
|
|
373
|
+
if known_single_iteration(loop.iter):
|
|
146
374
|
return None
|
|
147
375
|
value = loop.body[0].value
|
|
148
376
|
awaited = isinstance(value, ast.Await)
|
|
@@ -150,6 +378,18 @@ def discarded_call(loop: ast.For) -> tuple[ast.Call, bool] | None:
|
|
|
150
378
|
return (value, awaited) if isinstance(value, ast.Call) else None
|
|
151
379
|
|
|
152
380
|
|
|
381
|
+
def known_single_iteration(iterable: ast.expr) -> bool:
|
|
382
|
+
match iterable:
|
|
383
|
+
case ast.List(elts=items) | ast.Tuple(elts=items) | ast.Set(elts=items):
|
|
384
|
+
return len(items) <= 1 and not any(isinstance(item, ast.Starred) for item in items)
|
|
385
|
+
case ast.Dict(keys=keys):
|
|
386
|
+
return len(keys) <= 1 and None not in keys
|
|
387
|
+
case ast.Constant(value=str() | bytes() as value):
|
|
388
|
+
return len(value) <= 1
|
|
389
|
+
case _:
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
|
|
153
393
|
def depends_on_target(expression: ast.expr, target: ast.expr) -> bool:
|
|
154
394
|
names = _target_names(target)
|
|
155
395
|
return any(name in names for name in _free_names(expression))
|
|
@@ -187,11 +427,124 @@ def _comprehension_scopes(
|
|
|
187
427
|
yield from ((value, shadowed) for value in values)
|
|
188
428
|
|
|
189
429
|
|
|
430
|
+
@dataclass(frozen=True, slots=True)
|
|
431
|
+
class _ReceiverOrigin:
|
|
432
|
+
binding: ast.AST
|
|
433
|
+
attribute: str | None = None
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@dataclass(frozen=True, order=True, slots=True)
|
|
437
|
+
class _SourcePosition:
|
|
438
|
+
line: int
|
|
439
|
+
column: int
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class _ReceiverAliases:
|
|
443
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
444
|
+
self.context = context
|
|
445
|
+
self.provenance = context.static_resource_provenance
|
|
446
|
+
|
|
447
|
+
def origins(self, expression: ast.expr, remaining: int = 16) -> frozenset[_ReceiverOrigin] | None:
|
|
448
|
+
if remaining == 0:
|
|
449
|
+
return None
|
|
450
|
+
if isinstance(expression, ast.Attribute):
|
|
451
|
+
return self._injected_origin(expression)
|
|
452
|
+
if not isinstance(expression, ast.Name):
|
|
453
|
+
return frozenset()
|
|
454
|
+
bindings = self._preceding_bindings(expression)
|
|
455
|
+
origins: set[_ReceiverOrigin] = set()
|
|
456
|
+
for binding in bindings:
|
|
457
|
+
match binding:
|
|
458
|
+
case ast.arg():
|
|
459
|
+
origins.add(_ReceiverOrigin(binding))
|
|
460
|
+
case ast.Name():
|
|
461
|
+
assignment = self.context.parents.get(binding)
|
|
462
|
+
if (
|
|
463
|
+
isinstance(assignment, ast.Assign | ast.AnnAssign)
|
|
464
|
+
and assignment.value is not None
|
|
465
|
+
and binding
|
|
466
|
+
in (assignment.targets if isinstance(assignment, ast.Assign) else [assignment.target])
|
|
467
|
+
):
|
|
468
|
+
assigned = self.origins(assignment.value, remaining - 1)
|
|
469
|
+
if assigned is None:
|
|
470
|
+
return None
|
|
471
|
+
origins.update(assigned)
|
|
472
|
+
case _:
|
|
473
|
+
pass
|
|
474
|
+
return frozenset(origins)
|
|
475
|
+
|
|
476
|
+
def replaced(self, origin: _ReceiverOrigin, nodes: Iterable[ast.AST]) -> bool:
|
|
477
|
+
return any(
|
|
478
|
+
(origins := self.origins(target)) is None or origin in origins
|
|
479
|
+
for node in nodes
|
|
480
|
+
if (target := _replaced_receiver(node, self.context.imports)) is not None
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
def _preceding_bindings(self, expression: ast.Name) -> tuple[ast.AST, ...]:
|
|
484
|
+
scope = self.provenance.scope(expression)
|
|
485
|
+
while scope is not None:
|
|
486
|
+
bindings = self.provenance.bindings.get(scope, {}).get(expression.id)
|
|
487
|
+
if bindings:
|
|
488
|
+
return self._reaching_bindings(bindings, expression, scope)
|
|
489
|
+
scope = self.provenance.scope(scope)
|
|
490
|
+
return ()
|
|
491
|
+
|
|
492
|
+
def _reaching_bindings(
|
|
493
|
+
self, bindings: tuple[ast.AST, ...], expression: ast.Name, scope: ast.AST
|
|
494
|
+
) -> tuple[ast.AST, ...]:
|
|
495
|
+
preceding = sorted(
|
|
496
|
+
(binding for binding in bindings if self._precedes_read(binding, expression)), key=_source_position
|
|
497
|
+
)
|
|
498
|
+
# A definite local overwrite discards earlier possible objects; conditional
|
|
499
|
+
# writes retain all possible origins until another definite overwrite.
|
|
500
|
+
start = 0
|
|
501
|
+
for index, binding in enumerate(preceding):
|
|
502
|
+
statement = self.context.parents.get(binding)
|
|
503
|
+
if (
|
|
504
|
+
isinstance(binding, ast.arg)
|
|
505
|
+
or self.context.parents.get(binding) is scope
|
|
506
|
+
or (
|
|
507
|
+
isinstance(statement, ast.Assign | ast.AnnAssign | ast.Delete | ast.Import | ast.ImportFrom)
|
|
508
|
+
and self.context.parents.get(statement) is scope
|
|
509
|
+
)
|
|
510
|
+
):
|
|
511
|
+
start = index
|
|
512
|
+
return tuple(preceding[start:])
|
|
513
|
+
|
|
514
|
+
def _precedes_read(self, binding: ast.AST, expression: ast.Name) -> bool:
|
|
515
|
+
assignment = self.context.parents.get(binding)
|
|
516
|
+
return _source_position(binding) < _source_position(expression) and not (
|
|
517
|
+
isinstance(assignment, ast.Assign | ast.AnnAssign)
|
|
518
|
+
and (assignment.value is None or assignment.value is expression)
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def _injected_origin(self, expression: ast.Attribute) -> frozenset[_ReceiverOrigin]:
|
|
522
|
+
if not isinstance(expression.value, ast.Name) or expression.value.id != "self":
|
|
523
|
+
return frozenset()
|
|
524
|
+
binding = self.provenance.binding("self", expression)
|
|
525
|
+
arguments = self.context.parents.get(binding) if binding is not None else None
|
|
526
|
+
method = self.context.parents.get(arguments) if arguments is not None else None
|
|
527
|
+
owner = self.context.parents.get(method) if method is not None else None
|
|
528
|
+
if (
|
|
529
|
+
isinstance(binding, ast.arg)
|
|
530
|
+
and isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
531
|
+
and isinstance(owner, ast.ClassDef)
|
|
532
|
+
and [*method.args.posonlyargs, *method.args.args][:1] == [binding]
|
|
533
|
+
):
|
|
534
|
+
return frozenset({_ReceiverOrigin(owner, expression.attr)})
|
|
535
|
+
return frozenset()
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _source_position(node: ast.AST) -> _SourcePosition:
|
|
539
|
+
return _SourcePosition(getattr(node, "lineno", -1), getattr(node, "col_offset", -1))
|
|
540
|
+
|
|
541
|
+
|
|
190
542
|
class StoreOwners:
|
|
191
543
|
def __init__(self, context: PythonFileContext) -> None:
|
|
192
544
|
self.context = context
|
|
193
545
|
self.provenance = context.static_resource_provenance
|
|
194
546
|
self.project = context.session.project
|
|
547
|
+
self.aliases = _ReceiverAliases(context)
|
|
195
548
|
|
|
196
549
|
def receiver(self, expression: ast.expr, at: ast.AST) -> tuple[ast.ClassDef, ImportIndex] | None:
|
|
197
550
|
if isinstance(expression, ast.Name):
|
|
@@ -199,7 +552,7 @@ class StoreOwners:
|
|
|
199
552
|
if (
|
|
200
553
|
isinstance(binding, ast.arg)
|
|
201
554
|
and binding.annotation is not None
|
|
202
|
-
and not self._receiver_method_replaced(
|
|
555
|
+
and not self._receiver_method_replaced(binding)
|
|
203
556
|
):
|
|
204
557
|
return self.annotation(binding.annotation)
|
|
205
558
|
return None
|
|
@@ -214,13 +567,14 @@ class StoreOwners:
|
|
|
214
567
|
return None
|
|
215
568
|
arguments = self.context.parents.get(binding)
|
|
216
569
|
method = self.context.parents.get(arguments) if arguments is not None else None
|
|
570
|
+
owner = self.context.parents.get(method) if method is not None else None
|
|
217
571
|
if (
|
|
218
572
|
not isinstance(method, ast.FunctionDef | ast.AsyncFunctionDef)
|
|
219
|
-
or not isinstance(
|
|
573
|
+
or not isinstance(owner, ast.ClassDef)
|
|
220
574
|
or not (positional := [*method.args.posonlyargs, *method.args.args])
|
|
221
575
|
or positional[0] is not binding
|
|
222
576
|
or any(
|
|
223
|
-
self.context.module_imports
|
|
577
|
+
decorator_name(decorator, method, owner, self.context.module_imports) not in _NEUTRAL_DECORATORS
|
|
224
578
|
for decorator in method.decorator_list
|
|
225
579
|
)
|
|
226
580
|
):
|
|
@@ -228,16 +582,8 @@ class StoreOwners:
|
|
|
228
582
|
annotation = self._injected_annotation(expression.attr, at)
|
|
229
583
|
return self.annotation(annotation) if annotation is not None else None
|
|
230
584
|
|
|
231
|
-
def _receiver_method_replaced(self,
|
|
232
|
-
|
|
233
|
-
target = _replaced_receiver(node, self.context.imports)
|
|
234
|
-
if (
|
|
235
|
-
isinstance(target, ast.Name)
|
|
236
|
-
and target.id == receiver.id
|
|
237
|
-
and self.provenance.binding(receiver.id, target) is binding
|
|
238
|
-
):
|
|
239
|
-
return True
|
|
240
|
-
return False
|
|
585
|
+
def _receiver_method_replaced(self, binding: ast.arg) -> bool:
|
|
586
|
+
return self.aliases.replaced(_ReceiverOrigin(binding), self.context.nodes(ast.Attribute, ast.Call))
|
|
241
587
|
|
|
242
588
|
def _injected_annotation(self, attribute: str, at: ast.AST) -> ast.expr | None:
|
|
243
589
|
parent = self.context.parents.get(at)
|
|
@@ -252,7 +598,9 @@ class StoreOwners:
|
|
|
252
598
|
assignments = [
|
|
253
599
|
node for method in declared_methods for node in own_nodes(method) if _writes_self_attribute(node, attribute)
|
|
254
600
|
]
|
|
255
|
-
if len(assignments) != 1 or
|
|
601
|
+
if len(assignments) != 1 or self.aliases.replaced(
|
|
602
|
+
_ReceiverOrigin(parent, attribute), (node for method in declared_methods for node in own_nodes(method))
|
|
603
|
+
):
|
|
256
604
|
return None
|
|
257
605
|
value = _constructor_injected_value(constructor, assignments[0])
|
|
258
606
|
return _injected_parameter(constructor, value, self.provenance) if value is not None else None
|
|
@@ -276,7 +624,7 @@ class StoreOwners:
|
|
|
276
624
|
if isinstance(expression, ast.Name):
|
|
277
625
|
declaration = _declared_class(self.context.tree, expression.id)
|
|
278
626
|
if declaration is not None:
|
|
279
|
-
return declaration, self.context
|
|
627
|
+
return declaration, _decorator_imports(self.context, declaration)
|
|
280
628
|
return self._imported_annotation(expression)
|
|
281
629
|
|
|
282
630
|
def _imported_annotation(self, expression: ast.expr) -> tuple[ast.ClassDef, ImportIndex] | None:
|
|
@@ -301,7 +649,7 @@ class StoreOwners:
|
|
|
301
649
|
or symbol.name in provenance.mutated
|
|
302
650
|
):
|
|
303
651
|
return None
|
|
304
|
-
return declaration, definition
|
|
652
|
+
return declaration, _decorator_imports(definition, declaration)
|
|
305
653
|
|
|
306
654
|
|
|
307
655
|
def _writes_self_attribute(node: ast.AST, attribute: str) -> bool:
|
|
@@ -321,17 +669,6 @@ def _injection_constructor(owner: ast.ClassDef, attribute: str) -> Function | No
|
|
|
321
669
|
return None if attribute in scope_bound_names(runtime_statements) else methods(owner).get("__init__")
|
|
322
670
|
|
|
323
671
|
|
|
324
|
-
def _injected_receiver_replaced(declared: list[Function], attribute: str, imports: ImportIndex) -> bool:
|
|
325
|
-
return any(
|
|
326
|
-
isinstance(target := _replaced_receiver(node, imports), ast.Attribute)
|
|
327
|
-
and target.attr == attribute
|
|
328
|
-
and isinstance(target.value, ast.Name)
|
|
329
|
-
and target.value.id == "self"
|
|
330
|
-
for method in declared
|
|
331
|
-
for node in own_nodes(method)
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
672
|
def _constructor_injected_value(constructor: Function, assignment: ast.AST) -> ast.Name | None:
|
|
336
673
|
for statement in constructor.body:
|
|
337
674
|
match statement:
|
|
@@ -345,8 +682,7 @@ def _constructor_injected_value(constructor: Function, assignment: ast.AST) -> a
|
|
|
345
682
|
|
|
346
683
|
|
|
347
684
|
def _replaced_receiver(node: ast.AST, imports: ImportIndex) -> ast.expr | None:
|
|
348
|
-
|
|
349
|
-
if isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Store | ast.Del) and node.attr in members:
|
|
685
|
+
if isinstance(node, ast.Attribute) and isinstance(node.ctx, ast.Store | ast.Del) and node.attr in _STORE_MEMBERS:
|
|
350
686
|
return node.value
|
|
351
687
|
if not isinstance(node, ast.Call) or node.keywords:
|
|
352
688
|
return None
|
|
@@ -357,7 +693,7 @@ def _replaced_receiver(node: ast.AST, imports: ImportIndex) -> ast.expr | None:
|
|
|
357
693
|
if required is None or len(node.args) != required:
|
|
358
694
|
return None
|
|
359
695
|
member = node.args[1]
|
|
360
|
-
return node.args[0] if isinstance(member, ast.Constant) and member.value in
|
|
696
|
+
return node.args[0] if isinstance(member, ast.Constant) and member.value in _STORE_MEMBERS else None
|
|
361
697
|
|
|
362
698
|
|
|
363
699
|
def _injected_parameter(constructor: Function, value: ast.Name, provenance: ResourceProvenance) -> ast.expr | None:
|