sarj-python-lint 1.42.0__tar.gz → 1.42.1__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.42.0 → sarj_python_lint-1.42.1}/PKG-INFO +2 -2
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/pyproject.toml +2 -2
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_annotation_semantics.py +9 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_fastapi.py +4 -5
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +38 -29
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +6 -6
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +6 -6
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +19 -3
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +4 -6
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +39 -2
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_database_programmability.py +2 -2
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +16 -34
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +18 -3
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +13 -11
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_secret_in_log.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +4 -6
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_class_row.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +17 -10
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +10 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +2 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +1 -1
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/.gitignore +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/LICENSE +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/README.md +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.42.
|
|
3
|
+
Version: 1.42.1
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: packaging==26.3
|
|
21
21
|
Requires-Dist: ruff>=0.17.0
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.2.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.2.5
|
|
23
23
|
Requires-Dist: sqlglot==30.22.0
|
|
24
24
|
Requires-Dist: typer==0.27.3
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.42.
|
|
3
|
+
version = "1.42.1"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["sarj-rule-contracts==0.2.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.2.5", "packaging==26.3", "ruff>=0.17.0", "sqlglot==30.22.0", "typer==0.27.3"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -131,6 +131,15 @@ def _target_names(node: ast.expr) -> set[str]:
|
|
|
131
131
|
class _ScopeBindings(ast.NodeVisitor):
|
|
132
132
|
def __init__(self) -> None:
|
|
133
133
|
self.names: set[str] = set()
|
|
134
|
+
self._pending: list[ast.AST] = []
|
|
135
|
+
|
|
136
|
+
def visit(self, node: ast.AST) -> None:
|
|
137
|
+
self._pending.append(node)
|
|
138
|
+
while self._pending:
|
|
139
|
+
super().visit(self._pending.pop())
|
|
140
|
+
|
|
141
|
+
def generic_visit(self, node: ast.AST) -> None:
|
|
142
|
+
self._pending.extend(reversed(tuple(ast.iter_child_nodes(node))))
|
|
134
143
|
|
|
135
144
|
def visit_Name(self, node: ast.Name) -> None:
|
|
136
145
|
if isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
@@ -138,7 +138,9 @@ class FastapiIndex:
|
|
|
138
138
|
self._read_aliases(tree)
|
|
139
139
|
|
|
140
140
|
def _index_scopes(self, tree: ast.Module) -> None:
|
|
141
|
-
|
|
141
|
+
pending: list[tuple[ast.AST, int, int | None]] = [(tree, 0, None)]
|
|
142
|
+
while pending:
|
|
143
|
+
node, scope, class_owner = pending.pop()
|
|
142
144
|
self._node_scopes[id(node)] = scope
|
|
143
145
|
if isinstance(node, ast.ClassDef):
|
|
144
146
|
class_owner = node.lineno
|
|
@@ -149,10 +151,7 @@ class FastapiIndex:
|
|
|
149
151
|
self._route_scopes[id(node)] = scope
|
|
150
152
|
self._scope_parents[node.lineno] = scope
|
|
151
153
|
scope = node.lineno
|
|
152
|
-
for child in ast.iter_child_nodes(node)
|
|
153
|
-
visit(child, scope, class_owner)
|
|
154
|
-
|
|
155
|
-
visit(tree, 0, None)
|
|
154
|
+
pending.extend((child, scope, class_owner) for child in reversed(tuple(ast.iter_child_nodes(node))))
|
|
156
155
|
|
|
157
156
|
def _read_imports(self, tree: ast.Module) -> None:
|
|
158
157
|
for node in walk_ast(tree):
|
|
@@ -108,39 +108,48 @@ def inert_signature(node: ast.FunctionDef | ast.AsyncFunctionDef, context: Pytho
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _inert_expr(node: ast.AST, context: PythonFileContext) -> bool:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"pydantic.config.ConfigDict",
|
|
122
|
-
}:
|
|
111
|
+
pending = [node]
|
|
112
|
+
while pending:
|
|
113
|
+
current = pending.pop()
|
|
114
|
+
if isinstance(current, ast.Subscript) and not _inert_subscription(current, context):
|
|
115
|
+
return False
|
|
116
|
+
if isinstance(current, ast.Lambda):
|
|
117
|
+
if current.args.defaults or current.args.kw_defaults:
|
|
118
|
+
return False
|
|
119
|
+
continue
|
|
120
|
+
if isinstance(current, ast.Call) and not _inert_pydantic_call(current, context):
|
|
123
121
|
return False
|
|
124
|
-
if
|
|
125
|
-
|
|
122
|
+
if isinstance(
|
|
123
|
+
current,
|
|
124
|
+
(
|
|
125
|
+
ast.NamedExpr,
|
|
126
|
+
ast.Starred,
|
|
127
|
+
ast.Await,
|
|
128
|
+
ast.Yield,
|
|
129
|
+
ast.YieldFrom,
|
|
130
|
+
ast.ListComp,
|
|
131
|
+
ast.SetComp,
|
|
132
|
+
ast.DictComp,
|
|
133
|
+
ast.GeneratorExp,
|
|
134
|
+
),
|
|
126
135
|
):
|
|
127
136
|
return False
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
),
|
|
141
|
-
):
|
|
137
|
+
pending.extend(reversed(tuple(ast.iter_child_nodes(current))))
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _inert_pydantic_call(node: ast.Call, context: PythonFileContext) -> bool:
|
|
142
|
+
target = context.imports.resolved_qualified_name(node.func)
|
|
143
|
+
if target not in {
|
|
144
|
+
"pydantic.Field",
|
|
145
|
+
"pydantic.fields.Field",
|
|
146
|
+
"pydantic.ConfigDict",
|
|
147
|
+
"pydantic.config.ConfigDict",
|
|
148
|
+
}:
|
|
142
149
|
return False
|
|
143
|
-
return
|
|
150
|
+
return not any(keyword.arg is None for keyword in node.keywords) and not any(
|
|
151
|
+
isinstance(argument, ast.Starred) for argument in node.args
|
|
152
|
+
)
|
|
144
153
|
|
|
145
154
|
|
|
146
155
|
def _inert_subscription(node: ast.Subscript, context: PythonFileContext) -> bool:
|
|
@@ -128,14 +128,14 @@ class DocstringArgsRestateSignature(Rule):
|
|
|
128
128
|
imports: ImportIndex,
|
|
129
129
|
diags: list[Diagnostic],
|
|
130
130
|
) -> None:
|
|
131
|
-
for child in children(node)
|
|
131
|
+
pending: list[tuple[ast.AST, str | None]] = [(child, class_name) for child in reversed(children(node))]
|
|
132
|
+
while pending:
|
|
133
|
+
child, owner = pending.pop()
|
|
132
134
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
133
|
-
self._check_function(child,
|
|
134
|
-
self._walk(child, class_name, path, imports, diags)
|
|
135
|
+
self._check_function(child, owner, path, imports, diags)
|
|
135
136
|
elif isinstance(child, ast.ClassDef):
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self._walk(child, class_name, path, imports, diags)
|
|
137
|
+
owner = child.name
|
|
138
|
+
pending.extend((nested, owner) for nested in reversed(children(child)))
|
|
139
139
|
|
|
140
140
|
def _check_function(
|
|
141
141
|
self,
|
|
@@ -196,14 +196,14 @@ class DocstringReturnsRestateSignature(Rule):
|
|
|
196
196
|
imports: ImportIndex,
|
|
197
197
|
diags: list[Diagnostic],
|
|
198
198
|
) -> None:
|
|
199
|
-
for child in children(node)
|
|
199
|
+
pending: list[tuple[ast.AST, str | None]] = [(child, class_name) for child in reversed(children(node))]
|
|
200
|
+
while pending:
|
|
201
|
+
child, owner = pending.pop()
|
|
200
202
|
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
201
|
-
self._check_function(child,
|
|
202
|
-
self._walk(child, class_name, path, imports, diags)
|
|
203
|
+
self._check_function(child, owner, path, imports, diags)
|
|
203
204
|
elif isinstance(child, ast.ClassDef):
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
self._walk(child, class_name, path, imports, diags)
|
|
205
|
+
owner = child.name
|
|
206
|
+
pending.extend((nested, owner) for nested in reversed(children(child)))
|
|
207
207
|
|
|
208
208
|
def _check_function(
|
|
209
209
|
self,
|
|
@@ -62,7 +62,7 @@ class IacSourceCoupledTest(Rule):
|
|
|
62
62
|
files=(
|
|
63
63
|
ExampleFile.python(
|
|
64
64
|
"tests/test_policy.py",
|
|
65
|
-
"
|
|
65
|
+
"import json\n\ndef test_policy(rendered_plan_json: str):\n"
|
|
66
66
|
" plan = json.loads(rendered_plan_json)\n"
|
|
67
67
|
" changes = plan['resource_changes']\n"
|
|
68
68
|
" assert changes[0]['change']['actions'] == ['create']\n",
|
|
@@ -314,12 +314,24 @@ def _literal_key(value: object) -> _LiteralKey:
|
|
|
314
314
|
return _LiteralKey(type(value), value)
|
|
315
315
|
|
|
316
316
|
|
|
317
|
+
def _bit_union_members(node: ast.expr) -> list[ast.expr]:
|
|
318
|
+
members: list[ast.expr] = []
|
|
319
|
+
pending = [node]
|
|
320
|
+
while pending:
|
|
321
|
+
member = pending.pop()
|
|
322
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
323
|
+
pending.extend((member.right, member.left))
|
|
324
|
+
else:
|
|
325
|
+
members.append(member)
|
|
326
|
+
return members
|
|
327
|
+
|
|
328
|
+
|
|
317
329
|
def _literal_domain(node: ast.expr, imports: ImportIndex) -> _LiteralDomain | None:
|
|
318
330
|
node = _parse_forward_annotation(node)
|
|
319
331
|
if isinstance(node, ast.Constant) and node.value is None:
|
|
320
332
|
return _LiteralDomain(frozenset({_literal_key(None)}))
|
|
321
333
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
322
|
-
return _merge_domains((_literal_domain(
|
|
334
|
+
return _merge_domains(tuple(_literal_domain(member, imports) for member in _bit_union_members(node)))
|
|
323
335
|
if not isinstance(node, ast.Subscript):
|
|
324
336
|
return None
|
|
325
337
|
if imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Annotated"):
|
|
@@ -334,8 +346,12 @@ def _literal_domain(node: ast.expr, imports: ImportIndex) -> _LiteralDomain | No
|
|
|
334
346
|
return _merge_domains(tuple(_literal_domain(member, imports) for member in _slice_members(node.slice)))
|
|
335
347
|
if not imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Literal"):
|
|
336
348
|
return None
|
|
349
|
+
return _literal_members_domain(_slice_members(node.slice))
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _literal_members_domain(members: tuple[ast.expr, ...]) -> _LiteralDomain | None:
|
|
337
353
|
values: set[_LiteralKey] = set()
|
|
338
|
-
for member in
|
|
354
|
+
for member in members:
|
|
339
355
|
value = _literal_value(member)
|
|
340
356
|
if value is _MISSING or isinstance(value, float):
|
|
341
357
|
return None
|
|
@@ -514,7 +530,7 @@ def _numeric_bound_is_violated(name: str, default: float, bound: float) -> bool:
|
|
|
514
530
|
def _provably_non_null(node: ast.expr, imports: ImportIndex) -> bool:
|
|
515
531
|
node = _parse_forward_annotation(node)
|
|
516
532
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
517
|
-
return _provably_non_null(
|
|
533
|
+
return all(_provably_non_null(member, imports) for member in _bit_union_members(node))
|
|
518
534
|
if isinstance(node, ast.Name):
|
|
519
535
|
return node.id in _KNOWN_NON_NULL_BUILTINS and imports.builtin_is_unshadowed(node.id)
|
|
520
536
|
if not isinstance(node, ast.Subscript):
|
|
@@ -237,16 +237,14 @@ def _is_framework_hook(node: ast.FunctionDef | ast.AsyncFunctionDef, imports: Im
|
|
|
237
237
|
|
|
238
238
|
def _private_class_function_ids(tree: ast.Module) -> set[int]:
|
|
239
239
|
private: set[int] = set()
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
pending: list[tuple[ast.AST, bool]] = [(tree, False)]
|
|
241
|
+
while pending:
|
|
242
|
+
node, inside_private_class = pending.pop()
|
|
242
243
|
if isinstance(node, ast.ClassDef):
|
|
243
244
|
inside_private_class = inside_private_class or node.name.startswith("_")
|
|
244
245
|
if inside_private_class and isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
245
246
|
private.add(id(node))
|
|
246
|
-
for child in children(node)
|
|
247
|
-
visit(child, inside_private_class=inside_private_class)
|
|
248
|
-
|
|
249
|
-
visit(tree, inside_private_class=False)
|
|
247
|
+
pending.extend((child, inside_private_class) for child in reversed(children(node)))
|
|
250
248
|
return private
|
|
251
249
|
|
|
252
250
|
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from dataclasses import replace
|
|
5
|
+
from io import StringIO
|
|
5
6
|
from pathlib import PurePosixPath
|
|
7
|
+
import tokenize
|
|
6
8
|
from types import MappingProxyType
|
|
7
9
|
from typing import TYPE_CHECKING, ClassVar, final
|
|
8
10
|
|
|
@@ -187,8 +189,10 @@ class NoBroadKeywordCapture(Rule):
|
|
|
187
189
|
overloads = _overload_implementations(context, semantics, mutated_members)
|
|
188
190
|
if function in overloads:
|
|
189
191
|
continue
|
|
190
|
-
if
|
|
191
|
-
context.source_lines,
|
|
192
|
+
if (
|
|
193
|
+
is_suppressed(context.source_lines, argument.lineno, self.code)
|
|
194
|
+
or is_suppressed(context.source_lines, function.lineno, self.code)
|
|
195
|
+
or _signature_end_suppressed(function, context, self.code)
|
|
192
196
|
):
|
|
193
197
|
continue
|
|
194
198
|
findings.append(
|
|
@@ -207,6 +211,39 @@ class NoBroadKeywordCapture(Rule):
|
|
|
207
211
|
return sorted(findings, key=lambda finding: (finding.line, finding.col))
|
|
208
212
|
|
|
209
213
|
|
|
214
|
+
def _signature_end_suppressed(
|
|
215
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef, context: PythonFileContext, code: str
|
|
216
|
+
) -> bool:
|
|
217
|
+
first_statement = function.body[0]
|
|
218
|
+
header = context.source_lines[function.lineno - 1 : first_statement.lineno]
|
|
219
|
+
if not any("sarj-noqa" in line.lower() for line in header):
|
|
220
|
+
return False
|
|
221
|
+
# AST columns count UTF-8 bytes, and a continued inline body can share the header's logical line.
|
|
222
|
+
header[-1] = header[-1].encode("utf-8")[: first_statement.col_offset].decode("utf-8")
|
|
223
|
+
try:
|
|
224
|
+
line = _signature_end_line(header, function.lineno)
|
|
225
|
+
except tokenize.TokenError, SyntaxError:
|
|
226
|
+
return False
|
|
227
|
+
return line is not None and is_suppressed(context.source_lines, line, code)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _signature_end_line(header: list[str], start_line: int) -> int | None:
|
|
231
|
+
depth = 0
|
|
232
|
+
line = None
|
|
233
|
+
for token in tokenize.generate_tokens(StringIO("\n".join(header)).readline):
|
|
234
|
+
if token.type == tokenize.NEWLINE:
|
|
235
|
+
return line
|
|
236
|
+
if token.type != tokenize.OP:
|
|
237
|
+
continue
|
|
238
|
+
if token.string in {"(", "[", "{"}:
|
|
239
|
+
depth += 1
|
|
240
|
+
elif token.string in {")", "]", "}"}:
|
|
241
|
+
depth -= 1
|
|
242
|
+
elif token.string == ":" and depth == 0:
|
|
243
|
+
line = start_line + token.start[0] - 1
|
|
244
|
+
return line
|
|
245
|
+
|
|
246
|
+
|
|
210
247
|
def _interface_severity(
|
|
211
248
|
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
212
249
|
context: PythonFileContext,
|
|
@@ -29,8 +29,8 @@ _DATABASE_BEHAVIOR = re.compile(
|
|
|
29
29
|
r"CREATE\s+CONSTRAINT\s+TRIGGER|ALTER\s+TABLE\s+[^;]+?\s+ENABLE\s+(?:(?:ALWAYS|REPLICA)\s+)?TRIGGER)\b",
|
|
30
30
|
re.IGNORECASE,
|
|
31
31
|
)
|
|
32
|
-
_BAD = 'await conn.execute("CREATE TRIGGER fail_counter BEFORE UPDATE ON batch EXECUTE FUNCTION fail_counter()")\n'
|
|
33
|
-
_GOOD = 'await conn.execute("ALTER TABLE batch ADD CONSTRAINT fail_counter CHECK (false) NOT VALID")\n'
|
|
32
|
+
_BAD = 'async def install_fault(conn):\n await conn.execute("CREATE TRIGGER fail_counter BEFORE UPDATE ON batch EXECUTE FUNCTION fail_counter()")\n'
|
|
33
|
+
_GOOD = 'async def install_fault(conn):\n await conn.execute("ALTER TABLE batch ADD CONSTRAINT fail_counter CHECK (false) NOT VALID")\n'
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@final
|
{sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py
RENAMED
|
@@ -83,7 +83,7 @@ class NoLoguruExcInfo(Rule):
|
|
|
83
83
|
|
|
84
84
|
@override
|
|
85
85
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
86
|
-
if "exc_info" not in context.
|
|
86
|
+
if "exc_info" not in context.symbol_source or context.generated or context.tree is None:
|
|
87
87
|
return []
|
|
88
88
|
shadowed = _ambiguous_import_names(context)
|
|
89
89
|
if "*" in shadowed:
|
|
@@ -89,7 +89,7 @@ class NoLoguruPrintfFormat(Rule):
|
|
|
89
89
|
|
|
90
90
|
@override
|
|
91
91
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
92
|
-
if "loguru" not in context.
|
|
92
|
+
if "loguru" not in context.symbol_source or context.generated or context.tree is None:
|
|
93
93
|
return []
|
|
94
94
|
provenance: ResourceProvenance | None = None
|
|
95
95
|
diagnostics: list[Diagnostic] = []
|
|
@@ -386,40 +386,22 @@ def _has_dynamic_reflective_self_write(function: ast.FunctionDef | ast.AsyncFunc
|
|
|
386
386
|
|
|
387
387
|
def _function_scopes(tree: ast.Module) -> list[_FunctionScope]:
|
|
388
388
|
scopes: list[_FunctionScope] = []
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
@override
|
|
407
|
-
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
408
|
-
self._function(node)
|
|
409
|
-
|
|
410
|
-
def _function(self, node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
411
|
-
scopes.append(_FunctionScope(node, self.owner))
|
|
412
|
-
previous = self.owner
|
|
413
|
-
self.owner = None
|
|
414
|
-
for statement in node.body:
|
|
415
|
-
self.visit(statement)
|
|
416
|
-
self.owner = previous
|
|
417
|
-
|
|
418
|
-
@override
|
|
419
|
-
def visit_Lambda(self, node: ast.Lambda) -> None:
|
|
420
|
-
return
|
|
421
|
-
|
|
422
|
-
Collector().visit(tree)
|
|
389
|
+
pending: list[tuple[ast.AST, ast.ClassDef | None]] = [(tree, None)]
|
|
390
|
+
while pending:
|
|
391
|
+
node, owner = pending.pop()
|
|
392
|
+
match node:
|
|
393
|
+
case ast.Lambda():
|
|
394
|
+
continue
|
|
395
|
+
case ast.ClassDef():
|
|
396
|
+
owner = node
|
|
397
|
+
children = tuple(node.body)
|
|
398
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef():
|
|
399
|
+
scopes.append(_FunctionScope(node, owner))
|
|
400
|
+
owner = None
|
|
401
|
+
children = tuple(node.body)
|
|
402
|
+
case _:
|
|
403
|
+
children = tuple(ast.iter_child_nodes(node))
|
|
404
|
+
pending.extend((child, owner) for child in reversed(children))
|
|
423
405
|
return scopes
|
|
424
406
|
|
|
425
407
|
|
|
@@ -275,9 +275,7 @@ def _closed_domain(
|
|
|
275
275
|
if isinstance(node, ast.Name):
|
|
276
276
|
return local_domains.get(node.id)
|
|
277
277
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
278
|
-
|
|
279
|
-
right = _closed_domain(node.right, imports, local_domains)
|
|
280
|
-
return left | right if left is not None and right is not None else None
|
|
278
|
+
return _closed_union_domain(node, imports, local_domains)
|
|
281
279
|
if not isinstance(node, ast.Subscript):
|
|
282
280
|
return None
|
|
283
281
|
if imports.resolves(node.value, sources=_TYPING_SOURCES, symbol="Literal"):
|
|
@@ -288,6 +286,23 @@ def _closed_domain(
|
|
|
288
286
|
return None
|
|
289
287
|
|
|
290
288
|
|
|
289
|
+
def _closed_union_domain(
|
|
290
|
+
node: ast.expr, imports: ImportIndex, local_domains: dict[str, frozenset[str]]
|
|
291
|
+
) -> frozenset[str] | None:
|
|
292
|
+
values: set[str] = set()
|
|
293
|
+
pending = [node]
|
|
294
|
+
while pending:
|
|
295
|
+
member = pending.pop()
|
|
296
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
297
|
+
pending.extend((member.right, member.left))
|
|
298
|
+
continue
|
|
299
|
+
domain = _closed_domain(member, imports, local_domains)
|
|
300
|
+
if domain is None:
|
|
301
|
+
return None
|
|
302
|
+
values.update(domain)
|
|
303
|
+
return frozenset(values)
|
|
304
|
+
|
|
305
|
+
|
|
291
306
|
def _repeats_domain(description: str, domain: frozenset[str]) -> bool:
|
|
292
307
|
match = _DOMAIN_CLAUSE.search(description)
|
|
293
308
|
if match is None:
|
|
@@ -154,17 +154,19 @@ class NoRepeatedStringLiteral(Rule):
|
|
|
154
154
|
scope_of[id(node)] = scope
|
|
155
155
|
|
|
156
156
|
def visit(node: ast.AST, scope: int) -> None:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
pending = [(node, scope)]
|
|
158
|
+
while pending:
|
|
159
|
+
node, scope = pending.pop()
|
|
160
|
+
for annotation in _annotation_exprs(node):
|
|
161
|
+
excluded.update(id(child) for child in walk(annotation) if isinstance(child, ast.Constant))
|
|
162
|
+
if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
163
|
+
scope = declaration_scope(node, scope)
|
|
164
|
+
elif isinstance(node, ast.Lambda):
|
|
165
|
+
scope = id(node)
|
|
166
|
+
scope_line_of[scope] = node.lineno
|
|
167
|
+
else:
|
|
168
|
+
collect_literal(node, scope)
|
|
169
|
+
pending.extend((child, scope) for child in reversed(children(node)))
|
|
168
170
|
|
|
169
171
|
visit(tree, _MODULE_SCOPE)
|
|
170
172
|
|
{sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
@@ -103,7 +103,7 @@ class NoSecretInLog(Rule):
|
|
|
103
103
|
),
|
|
104
104
|
RuleExample(
|
|
105
105
|
example_id="redacted-logging-keyword",
|
|
106
|
-
title="
|
|
106
|
+
title="Only non-sensitive authentication metadata is logged",
|
|
107
107
|
outcome=ExampleOutcome.NO_MATCH,
|
|
108
108
|
files=(
|
|
109
109
|
ExampleFile.python(
|
|
@@ -218,15 +218,13 @@ def _is_syntax_required(owner: ast.Module | ast.ClassDef | ast.FunctionDef | ast
|
|
|
218
218
|
|
|
219
219
|
def _owner_keys(tree: ast.Module) -> dict[int, str]:
|
|
220
220
|
keys = {id(tree): "__doc__"}
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
pending: list[tuple[ast.AST, tuple[str, ...]]] = [(tree, ())]
|
|
222
|
+
while pending:
|
|
223
|
+
node, prefix = pending.pop()
|
|
223
224
|
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
224
225
|
prefix = (*prefix, node.name)
|
|
225
226
|
keys[id(node)] = ".".join(prefix)
|
|
226
|
-
for child in ast.iter_child_nodes(node)
|
|
227
|
-
visit(child, prefix)
|
|
228
|
-
|
|
229
|
-
visit(tree, ())
|
|
227
|
+
pending.extend((child, prefix) for child in reversed(tuple(ast.iter_child_nodes(node))))
|
|
230
228
|
return keys
|
|
231
229
|
|
|
232
230
|
|
{sarj_python_lint-1.42.0 → sarj_python_lint-1.42.1}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
@@ -90,7 +90,7 @@ class PreferClassRow(Rule):
|
|
|
90
90
|
files=(
|
|
91
91
|
ExampleFile.python(
|
|
92
92
|
"app/task_store.py",
|
|
93
|
-
|
|
93
|
+
'from psycopg.rows import class_row\n\nasync def load(conn):\n async with conn.cursor(row_factory=class_row(Task)) as cursor:\n await cursor.execute("SELECT id, state FROM task")\n return await cursor.fetchone()\n',
|
|
94
94
|
),
|
|
95
95
|
),
|
|
96
96
|
focus_path=PurePosixPath("app/task_store.py"),
|
|
@@ -116,7 +116,6 @@ class PreferDiscriminatedModelUnion(Rule):
|
|
|
116
116
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
117
117
|
if (
|
|
118
118
|
is_test_path(context.path)
|
|
119
|
-
or "after" not in context.source
|
|
120
119
|
or "None" not in context.symbol_source
|
|
121
120
|
or "model_validator" not in context.symbol_source
|
|
122
121
|
):
|
|
@@ -277,15 +276,23 @@ def _nullable(annotation: ast.expr, context: PythonFileContext) -> bool:
|
|
|
277
276
|
|
|
278
277
|
|
|
279
278
|
def _union_members(annotation: ast.expr, context: PythonFileContext) -> list[ast.expr]:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
279
|
+
members: list[ast.expr] = []
|
|
280
|
+
pending = [annotation]
|
|
281
|
+
while pending:
|
|
282
|
+
member = pending.pop()
|
|
283
|
+
if isinstance(member, ast.Subscript) and context.imports.resolves(
|
|
284
|
+
member.value, sources=TYPING_SOURCES, symbol="Optional"
|
|
285
|
+
):
|
|
286
|
+
if isinstance(member.slice, ast.Tuple):
|
|
287
|
+
members.append(member)
|
|
288
|
+
else:
|
|
289
|
+
members.append(ast.Constant(value=None))
|
|
290
|
+
pending.append(member.slice)
|
|
291
|
+
elif isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
292
|
+
pending.extend((member.right, member.left))
|
|
293
|
+
else:
|
|
294
|
+
members.append(member)
|
|
295
|
+
return members
|
|
289
296
|
|
|
290
297
|
|
|
291
298
|
def _non_none_witness(annotation: ast.expr, context: PythonFileContext) -> bool:
|
|
@@ -546,6 +546,16 @@ class _BoundNameCollector(ast.NodeVisitor):
|
|
|
546
546
|
self.names: set[str] = set()
|
|
547
547
|
self._include_imports: bool = include_imports
|
|
548
548
|
|
|
549
|
+
@override
|
|
550
|
+
def visit_BinOp(self, node: ast.BinOp) -> None:
|
|
551
|
+
pending: list[ast.AST] = [node]
|
|
552
|
+
while pending:
|
|
553
|
+
current = pending.pop()
|
|
554
|
+
if isinstance(current, ast.BinOp):
|
|
555
|
+
pending.extend((current.right, current.op, current.left))
|
|
556
|
+
else:
|
|
557
|
+
self.visit(current)
|
|
558
|
+
|
|
549
559
|
def _record_target(self, target: ast.expr) -> None:
|
|
550
560
|
match target:
|
|
551
561
|
case ast.Name(id=name):
|
|
@@ -159,8 +159,9 @@ class PreferSelfTypeAnnotation(Rule):
|
|
|
159
159
|
files=(
|
|
160
160
|
ExampleFile.python(
|
|
161
161
|
"app/builder.py",
|
|
162
|
-
"from typing import Self\n\n"
|
|
162
|
+
"from typing import Self, final\n\n"
|
|
163
163
|
"class Builder:\n"
|
|
164
|
+
" @final\n"
|
|
164
165
|
" def set_name(self, name: str) -> Self:\n"
|
|
165
166
|
" self.name = name\n"
|
|
166
167
|
" return self\n",
|
|
@@ -103,7 +103,7 @@ class PreferSetIsdisjoint(ProjectRule):
|
|
|
103
103
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
104
104
|
path = context.path
|
|
105
105
|
source = context.source
|
|
106
|
-
if ("&" not in source and "
|
|
106
|
+
if ("&" not in source and "intersection" not in context.symbol_source) or context.generated:
|
|
107
107
|
return []
|
|
108
108
|
tree = context.tree
|
|
109
109
|
if tree is None:
|
|
@@ -110,7 +110,7 @@ class RepeatedStaticCallCases(Rule):
|
|
|
110
110
|
files=(
|
|
111
111
|
ExampleFile.python(
|
|
112
112
|
"tests/test_parser.py",
|
|
113
|
-
"@pytest.mark.parametrize(('value', 'expected'), [('a', 1), ('b', 2), ('c', 3)])\ndef test_parse(value, expected):\n assert parse(value) == expected\n",
|
|
113
|
+
"import pytest\n\n@pytest.mark.parametrize(('value', 'expected'), [('a', 1), ('b', 2), ('c', 3)])\ndef test_parse(value, expected):\n assert parse(value) == expected\n",
|
|
114
114
|
),
|
|
115
115
|
),
|
|
116
116
|
focus_path=PurePosixPath("tests/test_parser.py"),
|