sarj-python-lint 1.81.0__tar.gz → 1.82.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.81.0 → sarj_python_lint-1.82.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/pyproject.toml +1 -1
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_async_lifecycle.py +124 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_numeric_proof.py +171 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_registry.py +10 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_executor_shutdown.py +135 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +221 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +161 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +289 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +370 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/.gitignore +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/LICENSE +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/README.md +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_comments.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_docs.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_inert.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_comment_narration.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_native_object_contracts.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_opaque_contracts.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.81.0 → sarj_python_lint-1.82.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.82.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/
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, final
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rules._resource_provenance import import_name_for_binding
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_INTROSPECTION = frozenset({"__dict__", "__setattr__", "__delattr__", "__getattribute__"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class _LocalAssignment:
|
|
19
|
+
binding: ast.Name
|
|
20
|
+
value: ast.expr
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
class AsyncApiProof:
|
|
25
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
26
|
+
self.context = context
|
|
27
|
+
self.provenance = context.static_resource_provenance
|
|
28
|
+
self._namespace_safety: dict[str, bool] = {}
|
|
29
|
+
self._origins: dict[str, set[str]] = {}
|
|
30
|
+
for node in context.nodes(ast.Import, ast.ImportFrom):
|
|
31
|
+
if isinstance(node, ast.Import):
|
|
32
|
+
for alias in node.names:
|
|
33
|
+
name = alias.asname or alias.name.partition(".")[0]
|
|
34
|
+
target = alias.name if alias.asname else alias.name.partition(".")[0]
|
|
35
|
+
self._origins.setdefault(name, set()).add(target)
|
|
36
|
+
elif node.level == 0 and node.module is not None:
|
|
37
|
+
for alias in node.names:
|
|
38
|
+
self._origins.setdefault(alias.asname or alias.name, set()).add(f"{node.module}.{alias.name}")
|
|
39
|
+
|
|
40
|
+
def imported(self, expression: ast.expr, qualified: str) -> bool:
|
|
41
|
+
root = expression
|
|
42
|
+
while isinstance(root, ast.Attribute):
|
|
43
|
+
root = root.value
|
|
44
|
+
if not isinstance(root, ast.Name):
|
|
45
|
+
return False
|
|
46
|
+
if (
|
|
47
|
+
self.context.dynamic_namespace
|
|
48
|
+
or self.provenance.wildcard
|
|
49
|
+
or root.id in self.provenance.mutated
|
|
50
|
+
or root.id in self.provenance.external_bindings
|
|
51
|
+
):
|
|
52
|
+
return False
|
|
53
|
+
binding = self.provenance.binding(root.id, expression)
|
|
54
|
+
if (
|
|
55
|
+
not isinstance(binding, ast.alias)
|
|
56
|
+
or not isinstance(self.provenance.scope(binding), ast.Module)
|
|
57
|
+
or import_name_for_binding(expression, binding, self.context.parents.get(binding)) != qualified
|
|
58
|
+
):
|
|
59
|
+
return False
|
|
60
|
+
declaration = self.context.parents.get(binding)
|
|
61
|
+
if not isinstance(declaration, ast.Import | ast.ImportFrom) or not isinstance(
|
|
62
|
+
self.context.parents.get(declaration), ast.Module
|
|
63
|
+
):
|
|
64
|
+
return False
|
|
65
|
+
if qualified not in self._namespace_safety:
|
|
66
|
+
self._namespace_safety[qualified] = self._namespace_untouched(qualified)
|
|
67
|
+
return self._namespace_safety[qualified]
|
|
68
|
+
|
|
69
|
+
def _namespace_untouched(self, qualified: str) -> bool:
|
|
70
|
+
for node in self.context.nodes(ast.Name, ast.Attribute):
|
|
71
|
+
targets = self._targets(node)
|
|
72
|
+
if not any(
|
|
73
|
+
target == qualified or target.startswith(qualified + ".") or qualified.startswith(target + ".")
|
|
74
|
+
for target in targets
|
|
75
|
+
):
|
|
76
|
+
continue
|
|
77
|
+
if not isinstance(node.ctx, ast.Load) or not self._safe_api_read(node, qualified):
|
|
78
|
+
return False
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
def _targets(self, expression: ast.Name | ast.Attribute) -> tuple[str, ...]:
|
|
82
|
+
parts: list[str] = []
|
|
83
|
+
root: ast.expr = expression
|
|
84
|
+
while isinstance(root, ast.Attribute):
|
|
85
|
+
parts.append(root.attr)
|
|
86
|
+
root = root.value
|
|
87
|
+
if not isinstance(root, ast.Name):
|
|
88
|
+
return ()
|
|
89
|
+
suffix = ".".join(reversed(parts))
|
|
90
|
+
return tuple(f"{origin}.{suffix}" if suffix else origin for origin in self._origins.get(root.id, ()))
|
|
91
|
+
|
|
92
|
+
def _safe_api_read(self, node: ast.Name | ast.Attribute, qualified: str) -> bool:
|
|
93
|
+
parent = self.context.parents.get(node)
|
|
94
|
+
if isinstance(parent, ast.Attribute) and parent.value is node:
|
|
95
|
+
return parent.attr not in _INTROSPECTION
|
|
96
|
+
if isinstance(parent, ast.Call) and parent.func is node:
|
|
97
|
+
return True
|
|
98
|
+
if isinstance(parent, ast.Subscript) and parent.value is node:
|
|
99
|
+
return True
|
|
100
|
+
if isinstance(parent, ast.arg | ast.AnnAssign) and parent.annotation is node:
|
|
101
|
+
return True
|
|
102
|
+
if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
103
|
+
return parent.returns is node or (
|
|
104
|
+
qualified == "contextlib.asynccontextmanager" and node in parent.decorator_list
|
|
105
|
+
)
|
|
106
|
+
return isinstance(parent, ast.ExceptHandler) and parent.type is node
|
|
107
|
+
|
|
108
|
+
def assignment(self, name: ast.Name, at: ast.AST) -> _LocalAssignment | None:
|
|
109
|
+
binding = self.provenance.binding(name.id, at)
|
|
110
|
+
if (
|
|
111
|
+
not isinstance(binding, ast.Name)
|
|
112
|
+
or name.id in self.provenance.mutated
|
|
113
|
+
or name.id in self.provenance.external_bindings
|
|
114
|
+
):
|
|
115
|
+
return None
|
|
116
|
+
value = self.provenance.assignment_value(binding, at)
|
|
117
|
+
return _LocalAssignment(binding=binding, value=value) if value is not None else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def method_receiver(call: ast.Call, methods: frozenset[str]) -> ast.Name | None:
|
|
121
|
+
callee = call.func
|
|
122
|
+
if isinstance(callee, ast.Attribute) and callee.attr in methods and isinstance(callee.value, ast.Name):
|
|
123
|
+
return callee.value
|
|
124
|
+
return None
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rules._resource_provenance import builtin_namespace_untouched
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_MAX_ALIAS_DEPTH = 8
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class LocalAssignment:
|
|
19
|
+
target: ast.Name
|
|
20
|
+
value: ast.expr
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def native_builtin(context: PythonFileContext, node: ast.expr, name: str) -> bool:
|
|
24
|
+
return (
|
|
25
|
+
isinstance(node, ast.Name)
|
|
26
|
+
and node.id == name
|
|
27
|
+
and context.imports.builtin_is_unshadowed(name)
|
|
28
|
+
and builtin_namespace_untouched(context)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def imported_api(context: PythonFileContext, node: ast.expr, qualified: str) -> bool:
|
|
33
|
+
if context.imports.resolved_qualified_name(node) != qualified:
|
|
34
|
+
return False
|
|
35
|
+
provenance = context.static_resource_provenance
|
|
36
|
+
root = node
|
|
37
|
+
while isinstance(root, ast.Attribute):
|
|
38
|
+
root = root.value
|
|
39
|
+
if not isinstance(root, ast.Name):
|
|
40
|
+
return False
|
|
41
|
+
binding = provenance.binding(root.id, node)
|
|
42
|
+
if not isinstance(binding, ast.alias) or provenance.scope(binding) is not context.tree:
|
|
43
|
+
return False
|
|
44
|
+
if root.id in provenance.mutated:
|
|
45
|
+
return False
|
|
46
|
+
module = qualified.partition(".")[0]
|
|
47
|
+
if not _module_namespace_intact(context, module):
|
|
48
|
+
return False
|
|
49
|
+
for read in provenance.binding_reads(root.id, binding):
|
|
50
|
+
parent = context.parents.get(read)
|
|
51
|
+
if not (isinstance(parent, ast.Attribute) or (isinstance(parent, ast.Call) and parent.func is read)):
|
|
52
|
+
return False
|
|
53
|
+
for reference in context.nodes(ast.Name, ast.Attribute):
|
|
54
|
+
if context.imports.resolved_qualified_name(reference) != qualified:
|
|
55
|
+
continue
|
|
56
|
+
parent = context.parents.get(reference)
|
|
57
|
+
if not isinstance(parent, ast.Call) or parent.func is not reference:
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _module_namespace_intact(context: PythonFileContext, module: str) -> bool:
|
|
63
|
+
for member in context.nodes(ast.Attribute):
|
|
64
|
+
target = context.imports.resolved_qualified_name(member)
|
|
65
|
+
if isinstance(member.ctx, ast.Store | ast.Del) and target is not None and target.startswith(module + "."):
|
|
66
|
+
return False
|
|
67
|
+
for statement in context.nodes(ast.Import):
|
|
68
|
+
for alias in statement.names:
|
|
69
|
+
if alias.name != module:
|
|
70
|
+
continue
|
|
71
|
+
if not _namespace_reads_are_members(context, alias):
|
|
72
|
+
return False
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _namespace_reads_are_members(context: PythonFileContext, alias: ast.alias) -> bool:
|
|
77
|
+
local = alias.asname or alias.name
|
|
78
|
+
for read in context.static_resource_provenance.binding_reads(local, alias):
|
|
79
|
+
parent = context.parents.get(read)
|
|
80
|
+
if not isinstance(parent, ast.Attribute) or parent.value is not read:
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def local_value(context: PythonFileContext, node: ast.Name) -> ast.expr | None:
|
|
86
|
+
provenance = context.static_resource_provenance
|
|
87
|
+
binding = provenance.binding(node.id, node)
|
|
88
|
+
if not isinstance(binding, ast.Name):
|
|
89
|
+
return None
|
|
90
|
+
return provenance.assignment_value(binding, node)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def value_origin(context: PythonFileContext, expression: ast.expr, *, depth: int = 0) -> str:
|
|
94
|
+
if isinstance(expression, ast.Name):
|
|
95
|
+
value = local_value(context, expression)
|
|
96
|
+
if isinstance(value, ast.Name) and depth < _MAX_ALIAS_DEPTH:
|
|
97
|
+
return value_origin(context, value, depth=depth + 1)
|
|
98
|
+
binding = context.static_resource_provenance.binding(expression.id, expression)
|
|
99
|
+
if isinstance(binding, ast.Name | ast.arg):
|
|
100
|
+
return f"binding:{binding.lineno}:{binding.col_offset}"
|
|
101
|
+
return ast.dump(expression)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def overridden_exit(context: PythonFileContext, node: ast.AST) -> bool:
|
|
105
|
+
parent = context.parents.get(node)
|
|
106
|
+
while parent is not None and not isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
107
|
+
if isinstance(parent, ast.Try | ast.TryStar) and any(
|
|
108
|
+
isinstance(nested, ast.Return | ast.Raise | ast.Break | ast.Continue)
|
|
109
|
+
for statement in parent.finalbody
|
|
110
|
+
for nested in ast.walk(statement)
|
|
111
|
+
):
|
|
112
|
+
return True
|
|
113
|
+
parent = context.parents.get(parent)
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def local_function(context: PythonFileContext, node: ast.expr) -> ast.FunctionDef | None:
|
|
118
|
+
if not isinstance(node, ast.Name):
|
|
119
|
+
return None
|
|
120
|
+
provenance = context.static_resource_provenance
|
|
121
|
+
binding = provenance.binding(node.id, node)
|
|
122
|
+
if not isinstance(binding, ast.FunctionDef) or binding.decorator_list:
|
|
123
|
+
return None
|
|
124
|
+
if provenance.scope(binding) is not context.tree or node.id in provenance.mutated:
|
|
125
|
+
return None
|
|
126
|
+
for read in provenance.binding_reads(node.id, binding):
|
|
127
|
+
parent = context.parents.get(read)
|
|
128
|
+
if not isinstance(parent, ast.Call) or parent.func is not read:
|
|
129
|
+
return None
|
|
130
|
+
return binding
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def function_body(function: ast.FunctionDef) -> list[ast.stmt]:
|
|
134
|
+
body = function.body
|
|
135
|
+
if (
|
|
136
|
+
body
|
|
137
|
+
and isinstance(body[0], ast.Expr)
|
|
138
|
+
and isinstance(body[0].value, ast.Constant)
|
|
139
|
+
and isinstance(body[0].value.value, str)
|
|
140
|
+
):
|
|
141
|
+
return body[1:]
|
|
142
|
+
return body
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def assignment(statement: ast.stmt) -> LocalAssignment | None:
|
|
146
|
+
match statement:
|
|
147
|
+
case ast.Assign(targets=[ast.Name() as target], value=value):
|
|
148
|
+
return LocalAssignment(target=target, value=value)
|
|
149
|
+
case ast.AnnAssign(target=ast.Name() as target, value=ast.expr() as value):
|
|
150
|
+
return LocalAssignment(target=target, value=value)
|
|
151
|
+
case _:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def null_test(expression: ast.expr, name: str) -> bool:
|
|
156
|
+
return (
|
|
157
|
+
isinstance(expression, ast.Compare)
|
|
158
|
+
and isinstance(expression.left, ast.Name)
|
|
159
|
+
and expression.left.id == name
|
|
160
|
+
and len(expression.ops) == 1
|
|
161
|
+
and isinstance(expression.ops[0], ast.Is)
|
|
162
|
+
and len(expression.comparators) == 1
|
|
163
|
+
and isinstance(expression.comparators[0], ast.Constant)
|
|
164
|
+
and expression.comparators[0].value is None
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def returns_none(statement: ast.stmt) -> bool:
|
|
169
|
+
return isinstance(statement, ast.Return) and (
|
|
170
|
+
statement.value is None or (isinstance(statement.value, ast.Constant) and statement.value.value is None)
|
|
171
|
+
)
|
|
@@ -216,10 +216,15 @@ if TYPE_CHECKING:
|
|
|
216
216
|
from collections.abc import Mapping
|
|
217
217
|
|
|
218
218
|
from sarj_python_lint.rule_base import Rule
|
|
219
|
+
from sarj_python_lint.rules.async_executor_shutdown import AsyncExecutorShutdown
|
|
220
|
+
from sarj_python_lint.rules.async_subprocess_cancellation_cleanup import AsyncSubprocessCancellationCleanup
|
|
221
|
+
from sarj_python_lint.rules.cancel_owned_task_without_join import CancelOwnedTaskWithoutJoin
|
|
219
222
|
from sarj_python_lint.rules.delete_route_returns_no_content import DeleteRouteReturnsNoContent
|
|
220
223
|
from sarj_python_lint.rules.duplicate_adjacent_exception_handler import DuplicateAdjacentExceptionHandler
|
|
221
224
|
from sarj_python_lint.rules.duplicate_closed_pure_helper import DuplicateClosedPureHelper
|
|
222
225
|
from sarj_python_lint.rules.duplicate_pytest_fixture_params import DuplicatePytestFixtureParams
|
|
226
|
+
from sarj_python_lint.rules.failed_probe_zero_threshold import FailedProbeZeroThreshold
|
|
227
|
+
from sarj_python_lint.rules.nan_hidden_by_builtin_min import NanHiddenByBuiltinMin
|
|
223
228
|
from sarj_python_lint.rules.no_conflicting_pydantic_alias_validation import NoConflictingPydanticAliasValidation
|
|
224
229
|
from sarj_python_lint.rules.no_contradictory_type_guard import NoContradictoryTypeGuard
|
|
225
230
|
from sarj_python_lint.rules.no_create_verb_in_post_path import NoCreateVerbInPostPath
|
|
@@ -256,6 +261,11 @@ from sarj_python_lint.rules.test_without_explicit_oracle import NoExplicitTestOr
|
|
|
256
261
|
|
|
257
262
|
REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
258
263
|
{
|
|
264
|
+
AsyncSubprocessCancellationCleanup.id: AsyncSubprocessCancellationCleanup,
|
|
265
|
+
CancelOwnedTaskWithoutJoin.id: CancelOwnedTaskWithoutJoin,
|
|
266
|
+
AsyncExecutorShutdown.id: AsyncExecutorShutdown,
|
|
267
|
+
NanHiddenByBuiltinMin.id: NanHiddenByBuiltinMin,
|
|
268
|
+
FailedProbeZeroThreshold.id: FailedProbeZeroThreshold,
|
|
259
269
|
PreferGroupedSettings.id: PreferGroupedSettings,
|
|
260
270
|
NoRedundantLocalAlias.id: NoRedundantLocalAlias,
|
|
261
271
|
PytestFixtureReturnsClosedResource.id: PytestFixtureReturnsClosedResource,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import (
|
|
8
|
+
AutofixPolicy,
|
|
9
|
+
Diagnostic,
|
|
10
|
+
ExampleFile,
|
|
11
|
+
ExampleOutcome,
|
|
12
|
+
Rule,
|
|
13
|
+
RuleCategory,
|
|
14
|
+
RuleDocumentation,
|
|
15
|
+
RuleExample,
|
|
16
|
+
Severity,
|
|
17
|
+
is_suppressed,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._async_lifecycle import AsyncApiProof, method_receiver
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_EXECUTORS = ("concurrent.futures.ThreadPoolExecutor", "concurrent.futures.ProcessPoolExecutor")
|
|
27
|
+
_BAD = "from concurrent.futures import ThreadPoolExecutor\n\nasync def close():\n executor = ThreadPoolExecutor()\n executor.shutdown(wait=True)\n"
|
|
28
|
+
_GOOD = "import asyncio\nfrom concurrent.futures import ThreadPoolExecutor\n\nasync def close():\n executor = ThreadPoolExecutor()\n await asyncio.to_thread(executor.shutdown, wait=True)\n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@final
|
|
32
|
+
class AsyncExecutorShutdown(Rule):
|
|
33
|
+
id = "async-executor-shutdown"
|
|
34
|
+
code = "SARJ540"
|
|
35
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
36
|
+
default_level=Severity.WARNING,
|
|
37
|
+
summary="Run waiting executor shutdown outside the async event-loop thread.",
|
|
38
|
+
rationale="Executor.shutdown waits for workers by default; invoking it directly in an async function can block the event loop and delay unrelated service work or cancellation.",
|
|
39
|
+
remediation="Await asyncio.to_thread(executor.shutdown, wait=True), or use the application's explicit nonblocking shutdown policy after arranging worker ownership and completion.",
|
|
40
|
+
category=RuleCategory.CORRECTNESS,
|
|
41
|
+
autofix=AutofixPolicy.NONE,
|
|
42
|
+
limitations=(
|
|
43
|
+
"Requires a stable direct assignment from an import-proven concurrent.futures ThreadPoolExecutor or ProcessPoolExecutor constructor and a direct shutdown call in an executing async function body.",
|
|
44
|
+
"Only omitted wait or literal wait=True is reported. Literal wait=False, dynamic arguments, nested synchronous callbacks, custom factories, borrowed receivers, context-managed bindings and explicit receiver/API mutation are excluded.",
|
|
45
|
+
"Stable enclosing-function and module bindings are recognized. Ordinary argument passing does not erase the constructor identity; external monkey-patching inside opaque consumers and runtime replacement of stdlib modules are not inferred.",
|
|
46
|
+
"Unknown bound-method consumers, receiver aliases/storage, conditional imports, namespace escapes, reflection, wildcard imports and generated files conservatively abstain. No automatic shutdown-policy change is offered.",
|
|
47
|
+
),
|
|
48
|
+
examples=(
|
|
49
|
+
RuleExample(
|
|
50
|
+
example_id="waiting-shutdown-in-async",
|
|
51
|
+
title="Waiting shutdown runs on the event-loop thread",
|
|
52
|
+
outcome=ExampleOutcome.MATCH,
|
|
53
|
+
files=(ExampleFile.python("app/lifecycle.py", _BAD),),
|
|
54
|
+
focus_path=PurePosixPath("app/lifecycle.py"),
|
|
55
|
+
expected_count=1,
|
|
56
|
+
public=True,
|
|
57
|
+
),
|
|
58
|
+
RuleExample(
|
|
59
|
+
example_id="shutdown-in-worker-thread",
|
|
60
|
+
title="Await waiting shutdown outside the event-loop thread",
|
|
61
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
62
|
+
files=(ExampleFile.python("app/lifecycle.py", _GOOD),),
|
|
63
|
+
focus_path=PurePosixPath("app/lifecycle.py"),
|
|
64
|
+
expected_count=0,
|
|
65
|
+
public=True,
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
description = documentation.summary
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
73
|
+
if "shutdown" not in context.symbol_source or context.generated or context.tree is None:
|
|
74
|
+
return []
|
|
75
|
+
proof = AsyncApiProof(context)
|
|
76
|
+
diagnostics: list[Diagnostic] = []
|
|
77
|
+
for call in context.nodes(ast.Call):
|
|
78
|
+
receiver = method_receiver(call, frozenset({"shutdown"}))
|
|
79
|
+
if receiver is None or not isinstance(proof.provenance.scope(call), ast.AsyncFunctionDef):
|
|
80
|
+
continue
|
|
81
|
+
if not _waiting_shutdown(call):
|
|
82
|
+
continue
|
|
83
|
+
assigned = proof.assignment(receiver, call)
|
|
84
|
+
if assigned is None:
|
|
85
|
+
continue
|
|
86
|
+
binding = assigned.binding
|
|
87
|
+
value = assigned.value
|
|
88
|
+
if not isinstance(value, ast.Call) or not any(proof.imported(value.func, name) for name in _EXECUTORS):
|
|
89
|
+
continue
|
|
90
|
+
if not _unchanged_receiver(proof, binding) or is_suppressed(context.source_lines, call.lineno, self.code):
|
|
91
|
+
continue
|
|
92
|
+
diagnostics.append(
|
|
93
|
+
Diagnostic(
|
|
94
|
+
path=context.path,
|
|
95
|
+
line=call.lineno,
|
|
96
|
+
col=call.col_offset + 1,
|
|
97
|
+
code=self.code,
|
|
98
|
+
severity=Severity.WARNING,
|
|
99
|
+
message="waiting Executor.shutdown runs on the event-loop thread; await an offloaded shutdown after arranging worker ownership",
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
return diagnostics
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _waiting_shutdown(call: ast.Call) -> bool:
|
|
106
|
+
if len(call.args) > 1 or any(keyword.arg not in {"wait", "cancel_futures"} for keyword in call.keywords):
|
|
107
|
+
return False
|
|
108
|
+
values = [*call.args, *(keyword.value for keyword in call.keywords if keyword.arg == "wait")]
|
|
109
|
+
return not values or (len(values) == 1 and isinstance(values[0], ast.Constant) and values[0].value is True)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _unchanged_receiver(proof: AsyncApiProof, binding: ast.Name) -> bool:
|
|
113
|
+
for read in proof.provenance.binding_reads(binding.id, binding):
|
|
114
|
+
parent = proof.context.parents.get(read)
|
|
115
|
+
if isinstance(parent, ast.Call) and read in parent.args:
|
|
116
|
+
continue
|
|
117
|
+
if isinstance(parent, ast.keyword) and isinstance(proof.context.parents.get(parent), ast.Call):
|
|
118
|
+
continue
|
|
119
|
+
if (
|
|
120
|
+
not isinstance(parent, ast.Attribute)
|
|
121
|
+
or parent.value is not read
|
|
122
|
+
or parent.attr in {"__dict__", "__setattr__", "__delattr__", "__getattribute__"}
|
|
123
|
+
):
|
|
124
|
+
return False
|
|
125
|
+
consumer = proof.context.parents.get(parent)
|
|
126
|
+
if isinstance(consumer, ast.Call) and consumer.func is parent:
|
|
127
|
+
continue
|
|
128
|
+
if (
|
|
129
|
+
isinstance(consumer, ast.Call)
|
|
130
|
+
and parent in consumer.args
|
|
131
|
+
and proof.imported(consumer.func, "asyncio.to_thread")
|
|
132
|
+
):
|
|
133
|
+
continue
|
|
134
|
+
return False
|
|
135
|
+
return True
|