sarj-python-lint 1.37.0__tar.gz → 1.39.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.37.0 → sarj_python_lint-1.39.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_registry.py +4 -0
- sarj_python_lint-1.39.0/src/sarj_python_lint/rules/no_async_function_cache.py +179 -0
- sarj_python_lint-1.39.0/src/sarj_python_lint/rules/no_loguru_exc_info.py +178 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/.gitignore +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/LICENSE +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/README.md +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/unused_test_factory_option.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.39.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/
|
|
@@ -32,6 +32,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import (
|
|
|
32
32
|
NoAnalyticalAggregationInPostgresStore,
|
|
33
33
|
)
|
|
34
34
|
from sarj_python_lint.rules.no_any_mapping_types import NoAnyMappingTypes
|
|
35
|
+
from sarj_python_lint.rules.no_async_function_cache import NoAsyncFunctionCache
|
|
35
36
|
from sarj_python_lint.rules.no_before_validator_input_mutation import NoBeforeValidatorInputMutation
|
|
36
37
|
from sarj_python_lint.rules.no_broad_keyword_capture import NoBroadKeywordCapture
|
|
37
38
|
from sarj_python_lint.rules.no_comment_cruft import NoCommentCruft
|
|
@@ -65,6 +66,7 @@ from sarj_python_lint.rules.no_injected_module_loader import NoInjectedModuleLoa
|
|
|
65
66
|
from sarj_python_lint.rules.no_input_model_mutation import NoInputModelMutation
|
|
66
67
|
from sarj_python_lint.rules.no_interpreter_source_arguments import NoInterpreterSourceArguments
|
|
67
68
|
from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvalidArgumentNameSuppression
|
|
69
|
+
from sarj_python_lint.rules.no_loguru_exc_info import NoLoguruExcInfo
|
|
68
70
|
from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
|
|
69
71
|
from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
|
|
70
72
|
from sarj_python_lint.rules.no_nullable_dependency_fallback import NoNullableDependencyFallback
|
|
@@ -241,6 +243,8 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
241
243
|
NoSwallowedAsyncioCancellation.id: NoSwallowedAsyncioCancellation,
|
|
242
244
|
RequireExplicitPsycopgTransaction.id: RequireExplicitPsycopgTransaction,
|
|
243
245
|
AsyncMockCallWithoutAwaitAssertion.id: AsyncMockCallWithoutAwaitAssertion,
|
|
246
|
+
NoAsyncFunctionCache.id: NoAsyncFunctionCache,
|
|
247
|
+
NoLoguruExcInfo.id: NoLoguruExcInfo,
|
|
244
248
|
ComplexPostgresQueryRequiresArchitectureReview.id: ComplexPostgresQueryRequiresArchitectureReview,
|
|
245
249
|
ExcessiveCommentary.id: ExcessiveCommentary,
|
|
246
250
|
PreferWalrusAwaitedNoneGuard.id: PreferWalrusAwaitedNoneGuard,
|
|
@@ -0,0 +1,179 @@
|
|
|
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._ast_index import children
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_FUNCTOOLS = frozenset({"functools"})
|
|
28
|
+
_COROUTINE_CACHES = frozenset({"cache", "cached_property", "lru_cache"})
|
|
29
|
+
_LRU_CACHE_POSITIONAL_ARGUMENTS = 2
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@final
|
|
33
|
+
class NoAsyncFunctionCache(Rule):
|
|
34
|
+
id = "no-async-function-cache"
|
|
35
|
+
code = "SARJ507"
|
|
36
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
37
|
+
default_level=Severity.WARNING,
|
|
38
|
+
summary="Standard library cache decorates a coroutine function and retains single-use coroutine objects.",
|
|
39
|
+
rationale=(
|
|
40
|
+
"`functools.cache`, `lru_cache`, and `cached_property` store the coroutine object returned by an async "
|
|
41
|
+
"function rather than its awaited value. A subsequent cache hit can raise `RuntimeError: cannot reuse "
|
|
42
|
+
"already awaited coroutine`."
|
|
43
|
+
),
|
|
44
|
+
remediation="Remove the standard library cache or use an async-aware cache that stores completed results.",
|
|
45
|
+
category=RuleCategory.CORRECTNESS,
|
|
46
|
+
autofix=AutofixPolicy.NONE,
|
|
47
|
+
limitations=(
|
|
48
|
+
"Only a proven functools import used as the innermost decorator on a coroutine function is reported.",
|
|
49
|
+
"Async generators, transformed functions, ambiguous or rebound imports, and generated files are excluded.",
|
|
50
|
+
"Wildcard imports and names rebound by relative imports or imports outside the module body are excluded conservatively.",
|
|
51
|
+
"lru_cache with disabled, dynamic, or unpacked maxsize arguments is excluded; an omitted size or literal positive integer or None is required.",
|
|
52
|
+
"Indirect wrapping calls and runtime monkeypatches are not resolved. Test code is checked because coroutine reuse fails there too.",
|
|
53
|
+
"No automatic rewrite is offered because cache lifetime, cancellation, and concurrency require an explicit design decision.",
|
|
54
|
+
),
|
|
55
|
+
examples=(
|
|
56
|
+
RuleExample(
|
|
57
|
+
example_id="cached-coroutine-function",
|
|
58
|
+
title="A standard cache retains an exhausted coroutine",
|
|
59
|
+
outcome=ExampleOutcome.MATCH,
|
|
60
|
+
files=(
|
|
61
|
+
ExampleFile.python(
|
|
62
|
+
"app/cache.py",
|
|
63
|
+
"from functools import cache\n\n@cache\nasync def load() -> int:\n return 1\n",
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
focus_path=PurePosixPath("app/cache.py"),
|
|
67
|
+
expected_count=1,
|
|
68
|
+
public=True,
|
|
69
|
+
),
|
|
70
|
+
RuleExample(
|
|
71
|
+
example_id="fresh-coroutine-function",
|
|
72
|
+
title="Each call creates a fresh coroutine",
|
|
73
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
74
|
+
files=(ExampleFile.python("app/cache.py", "async def load() -> int:\n return 1\n"),),
|
|
75
|
+
focus_path=PurePosixPath("app/cache.py"),
|
|
76
|
+
expected_count=0,
|
|
77
|
+
public=True,
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
description = documentation.summary
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
85
|
+
if "async" not in context.source or context.generated or context.tree is None:
|
|
86
|
+
return []
|
|
87
|
+
shadowed = _nested_import_names(context)
|
|
88
|
+
if "*" in shadowed:
|
|
89
|
+
return []
|
|
90
|
+
diagnostics: list[Diagnostic] = []
|
|
91
|
+
for function in context.nodes(ast.AsyncFunctionDef):
|
|
92
|
+
if not function.decorator_list:
|
|
93
|
+
continue
|
|
94
|
+
decorator = function.decorator_list[-1]
|
|
95
|
+
if (
|
|
96
|
+
not _is_coroutine_cache(decorator, context.imports, shadowed)
|
|
97
|
+
or _has_own_yield(function)
|
|
98
|
+
or is_suppressed(context.source_lines, decorator.lineno, self.code)
|
|
99
|
+
):
|
|
100
|
+
continue
|
|
101
|
+
diagnostics.append(
|
|
102
|
+
Diagnostic(
|
|
103
|
+
path=context.path,
|
|
104
|
+
line=decorator.lineno,
|
|
105
|
+
col=decorator.col_offset + 1,
|
|
106
|
+
code=self.code,
|
|
107
|
+
message=(
|
|
108
|
+
"Standard library cache retains a single-use coroutine; remove it or use an async-aware "
|
|
109
|
+
"cache that stores awaited results."
|
|
110
|
+
),
|
|
111
|
+
severity=Severity.WARNING,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
return diagnostics
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _nested_import_names(context: PythonFileContext) -> frozenset[str]:
|
|
118
|
+
return frozenset(
|
|
119
|
+
alias.asname or alias.name.partition(".")[0]
|
|
120
|
+
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
121
|
+
for alias in node.names
|
|
122
|
+
if context.parents[node] is not context.tree
|
|
123
|
+
or (isinstance(node, ast.ImportFrom) and node.level > 0)
|
|
124
|
+
or alias.name == "*"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_coroutine_cache(decorator: ast.expr, imports: ImportIndex, shadowed: frozenset[str]) -> bool:
|
|
129
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
130
|
+
root = target
|
|
131
|
+
while isinstance(root, ast.Attribute):
|
|
132
|
+
root = root.value
|
|
133
|
+
if isinstance(root, ast.Name) and root.id in shadowed:
|
|
134
|
+
return False
|
|
135
|
+
symbol = imports.resolved_symbol(target, sources=_FUNCTOOLS)
|
|
136
|
+
if symbol not in _COROUTINE_CACHES:
|
|
137
|
+
return False
|
|
138
|
+
if not isinstance(decorator, ast.Call):
|
|
139
|
+
return True
|
|
140
|
+
if symbol != "lru_cache" or any(keyword.arg is None for keyword in decorator.keywords):
|
|
141
|
+
return False
|
|
142
|
+
if len(decorator.args) > _LRU_CACHE_POSITIONAL_ARGUMENTS or any(
|
|
143
|
+
isinstance(argument, ast.Starred) for argument in decorator.args
|
|
144
|
+
):
|
|
145
|
+
return False
|
|
146
|
+
sizes = [keyword.value for keyword in decorator.keywords if keyword.arg == "maxsize"]
|
|
147
|
+
if decorator.args:
|
|
148
|
+
sizes.append(decorator.args[0])
|
|
149
|
+
if not sizes:
|
|
150
|
+
return True
|
|
151
|
+
if len(sizes) != 1:
|
|
152
|
+
return False
|
|
153
|
+
match sizes[0]:
|
|
154
|
+
case ast.Constant(value=None):
|
|
155
|
+
return True
|
|
156
|
+
case ast.Constant(value=int() as size) | ast.UnaryOp(op=ast.UAdd(), operand=ast.Constant(value=int() as size)):
|
|
157
|
+
return size > 0
|
|
158
|
+
case _:
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _has_own_yield(function: ast.AsyncFunctionDef) -> bool:
|
|
163
|
+
pending: list[ast.AST] = list(function.body)
|
|
164
|
+
while pending:
|
|
165
|
+
match pending.pop():
|
|
166
|
+
case ast.Yield() | ast.YieldFrom():
|
|
167
|
+
return True
|
|
168
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.Lambda() as definition:
|
|
169
|
+
pending.extend(definition.args.defaults)
|
|
170
|
+
pending.extend(default for default in definition.args.kw_defaults if default is not None)
|
|
171
|
+
if not isinstance(definition, ast.Lambda):
|
|
172
|
+
pending.extend(definition.decorator_list)
|
|
173
|
+
case ast.ClassDef() as definition:
|
|
174
|
+
pending.extend(definition.bases)
|
|
175
|
+
pending.extend(keyword.value for keyword in definition.keywords)
|
|
176
|
+
pending.extend(definition.decorator_list)
|
|
177
|
+
case node:
|
|
178
|
+
pending.extend(children(node))
|
|
179
|
+
return False
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from string import Formatter
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.rule_base import (
|
|
9
|
+
AutofixPolicy,
|
|
10
|
+
Diagnostic,
|
|
11
|
+
ExampleFile,
|
|
12
|
+
ExampleOutcome,
|
|
13
|
+
Rule,
|
|
14
|
+
RuleCategory,
|
|
15
|
+
RuleDocumentation,
|
|
16
|
+
RuleExample,
|
|
17
|
+
Severity,
|
|
18
|
+
is_suppressed,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_METHODS = frozenset({"trace", "debug", "info", "success", "warning", "error", "critical", "log"})
|
|
27
|
+
_LOGURU = frozenset({"loguru"})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@final
|
|
31
|
+
class NoLoguruExcInfo(Rule):
|
|
32
|
+
id = "no-loguru-exc-info"
|
|
33
|
+
code = "SARJ508"
|
|
34
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
35
|
+
default_level=Severity.WARNING,
|
|
36
|
+
summary="Loguru exc_info=True adds a field rather than retaining the exception traceback.",
|
|
37
|
+
rationale=(
|
|
38
|
+
"Loguru accepts logging keyword arguments as structured fields. The standard logging `exc_info=True` "
|
|
39
|
+
"argument therefore does not attach an exception, leaving failure logs without their traceback."
|
|
40
|
+
),
|
|
41
|
+
remediation="Use `logger.opt(exception=True)` or `logger.exception(...)` when reporting the active exception.",
|
|
42
|
+
category=RuleCategory.CORRECTNESS,
|
|
43
|
+
autofix=AutofixPolicy.NONE,
|
|
44
|
+
limitations=(
|
|
45
|
+
"Only unambiguous Loguru imports and immediate bind/opt chains with literal exc_info=True are checked.",
|
|
46
|
+
"A literal message is required; templates using exc_info or the record, explicit exception capture, and exception() are excluded.",
|
|
47
|
+
"Unknown receivers, stored logger aliases, dynamic exc_info values, ambiguous imports, and generated files are excluded.",
|
|
48
|
+
"Runtime monkeypatches of Loguru objects or methods are not resolved.",
|
|
49
|
+
"An intentionally stored field can use a diagnostic-specific suppression. No automatic rewrite changes logging context.",
|
|
50
|
+
),
|
|
51
|
+
examples=(
|
|
52
|
+
RuleExample(
|
|
53
|
+
example_id="stdlib-argument-on-loguru",
|
|
54
|
+
title="A standard logging argument does not capture the exception",
|
|
55
|
+
outcome=ExampleOutcome.MATCH,
|
|
56
|
+
files=(
|
|
57
|
+
ExampleFile.python(
|
|
58
|
+
"app/service.py",
|
|
59
|
+
'from loguru import logger\ntry:\n int("invalid")\nexcept ValueError:\n logger.warning("failed", exc_info=True)\n',
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
63
|
+
expected_count=1,
|
|
64
|
+
public=True,
|
|
65
|
+
),
|
|
66
|
+
RuleExample(
|
|
67
|
+
example_id="loguru-exception-option",
|
|
68
|
+
title="Loguru captures the exception through opt",
|
|
69
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
70
|
+
files=(
|
|
71
|
+
ExampleFile.python(
|
|
72
|
+
"app/service.py",
|
|
73
|
+
'from loguru import logger\ntry:\n int("invalid")\nexcept ValueError:\n logger.opt(exception=True).warning("failed")\n',
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
focus_path=PurePosixPath("app/service.py"),
|
|
77
|
+
expected_count=0,
|
|
78
|
+
public=True,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
description = documentation.summary
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
86
|
+
if "exc_info" not in context.source or context.generated or context.tree is None:
|
|
87
|
+
return []
|
|
88
|
+
shadowed = _ambiguous_import_names(context)
|
|
89
|
+
if "*" in shadowed:
|
|
90
|
+
return []
|
|
91
|
+
diagnostics: list[Diagnostic] = []
|
|
92
|
+
for call in context.nodes(ast.Call):
|
|
93
|
+
if not isinstance(call.func, ast.Attribute) or call.func.attr not in _METHODS:
|
|
94
|
+
continue
|
|
95
|
+
keyword = next((item for item in call.keywords if item.arg == "exc_info"), None)
|
|
96
|
+
if keyword is None or not isinstance(keyword.value, ast.Constant) or keyword.value.value is not True:
|
|
97
|
+
continue
|
|
98
|
+
if (
|
|
99
|
+
not _is_unconfigured_loguru(call.func.value, context, shadowed)
|
|
100
|
+
or not _has_unused_exc_info(call)
|
|
101
|
+
or is_suppressed(context.source_lines, keyword.lineno, self.code)
|
|
102
|
+
):
|
|
103
|
+
continue
|
|
104
|
+
diagnostics.append(
|
|
105
|
+
Diagnostic(
|
|
106
|
+
path=context.path,
|
|
107
|
+
line=keyword.lineno,
|
|
108
|
+
col=keyword.col_offset + 1,
|
|
109
|
+
code=self.code,
|
|
110
|
+
message="Loguru treats exc_info=True as a field; use opt(exception=True) or exception() to retain the traceback.",
|
|
111
|
+
severity=Severity.WARNING,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
return diagnostics
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _ambiguous_import_names(context: PythonFileContext) -> frozenset[str]:
|
|
118
|
+
return frozenset(
|
|
119
|
+
alias.asname or alias.name.partition(".")[0]
|
|
120
|
+
for node in context.nodes(ast.Import, ast.ImportFrom)
|
|
121
|
+
for alias in node.names
|
|
122
|
+
if context.parents[node] is not context.tree
|
|
123
|
+
or (isinstance(node, ast.ImportFrom) and node.level > 0)
|
|
124
|
+
or alias.name == "*"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_unconfigured_loguru(receiver: ast.expr, context: PythonFileContext, shadowed: frozenset[str]) -> bool:
|
|
129
|
+
opted = False
|
|
130
|
+
while isinstance(receiver, ast.Call) and isinstance(receiver.func, ast.Attribute):
|
|
131
|
+
method = receiver.func.attr
|
|
132
|
+
if method not in {"bind", "opt"}:
|
|
133
|
+
return False
|
|
134
|
+
if method == "opt" and not opted:
|
|
135
|
+
opted = True
|
|
136
|
+
if any(
|
|
137
|
+
keyword.arg is None
|
|
138
|
+
or (
|
|
139
|
+
keyword.arg == "exception"
|
|
140
|
+
and not (isinstance(keyword.value, ast.Constant) and not keyword.value.value)
|
|
141
|
+
)
|
|
142
|
+
for keyword in receiver.keywords
|
|
143
|
+
):
|
|
144
|
+
return False
|
|
145
|
+
receiver = receiver.func.value
|
|
146
|
+
root = receiver
|
|
147
|
+
while isinstance(root, ast.Attribute):
|
|
148
|
+
root = root.value
|
|
149
|
+
return not (isinstance(root, ast.Name) and root.id in shadowed) and context.imports.resolves(
|
|
150
|
+
receiver, sources=_LOGURU, symbol="logger"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _has_unused_exc_info(call: ast.Call) -> bool:
|
|
155
|
+
message_index = int(isinstance(call.func, ast.Attribute) and call.func.attr == "log")
|
|
156
|
+
message = (
|
|
157
|
+
call.args[message_index]
|
|
158
|
+
if len(call.args) > message_index
|
|
159
|
+
else next((keyword.value for keyword in call.keywords if keyword.arg == "message"), None)
|
|
160
|
+
)
|
|
161
|
+
if not isinstance(message, ast.Constant) or not isinstance(message.value, str):
|
|
162
|
+
return False
|
|
163
|
+
return not _template_may_use_exc_info(message.value)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _template_may_use_exc_info(template: str) -> bool:
|
|
167
|
+
pending = [template]
|
|
168
|
+
try:
|
|
169
|
+
while pending:
|
|
170
|
+
for _, field, spec, _ in Formatter().parse(pending.pop()):
|
|
171
|
+
if field is not None:
|
|
172
|
+
if field.partition(".")[0].partition("[")[0] in {"exc_info", "record"}:
|
|
173
|
+
return True
|
|
174
|
+
if spec:
|
|
175
|
+
pending.append(spec)
|
|
176
|
+
except ValueError:
|
|
177
|
+
return True
|
|
178
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.37.0 → sarj_python_lint-1.39.0}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|